001/******************************************************************************* 002 * Copyright 2017 The MIT Internet Trust Consortium 003 * 004 * Portions copyright 2011-2013 The MITRE Corporation 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 *******************************************************************************/ 018/** 019 * 020 */ 021package org.mitre.openid.connect.client.service.impl; 022 023import static org.mitre.util.JsonUtils.getAsBoolean; 024import static org.mitre.util.JsonUtils.getAsEncryptionMethodList; 025import static org.mitre.util.JsonUtils.getAsJweAlgorithmList; 026import static org.mitre.util.JsonUtils.getAsJwsAlgorithmList; 027import static org.mitre.util.JsonUtils.getAsString; 028import static org.mitre.util.JsonUtils.getAsStringList; 029 030import java.util.HashSet; 031import java.util.Set; 032import java.util.concurrent.ExecutionException; 033 034import org.apache.http.client.HttpClient; 035import org.apache.http.impl.client.HttpClientBuilder; 036import org.mitre.openid.connect.client.service.ServerConfigurationService; 037import org.mitre.openid.connect.config.ServerConfiguration; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 041import org.springframework.security.authentication.AuthenticationServiceException; 042import org.springframework.web.client.RestTemplate; 043 044import com.google.common.cache.CacheBuilder; 045import com.google.common.cache.CacheLoader; 046import com.google.common.cache.LoadingCache; 047import com.google.common.util.concurrent.UncheckedExecutionException; 048import com.google.gson.JsonElement; 049import com.google.gson.JsonObject; 050import com.google.gson.JsonParser; 051 052/** 053 * 054 * Dynamically fetches OpenID Connect server configurations based on the issuer. Caches the server configurations. 055 * 056 * @author jricher 057 * 058 */ 059public class DynamicServerConfigurationService implements ServerConfigurationService { 060 061 /** 062 * Logger for this class 063 */ 064 private static final Logger logger = LoggerFactory.getLogger(DynamicServerConfigurationService.class); 065 066 // map of issuer -> server configuration, loaded dynamically from service discovery 067 private LoadingCache<String, ServerConfiguration> servers; 068 069 private Set<String> whitelist = new HashSet<>(); 070 private Set<String> blacklist = new HashSet<>(); 071 072 public DynamicServerConfigurationService() { 073 this(HttpClientBuilder.create().useSystemProperties().build()); 074 } 075 076 public DynamicServerConfigurationService(HttpClient httpClient) { 077 // initialize the cache 078 servers = CacheBuilder.newBuilder().build(new OpenIDConnectServiceConfigurationFetcher(httpClient)); 079 } 080 081 /** 082 * @return the whitelist 083 */ 084 public Set<String> getWhitelist() { 085 return whitelist; 086 } 087 088 /** 089 * @param whitelist the whitelist to set 090 */ 091 public void setWhitelist(Set<String> whitelist) { 092 this.whitelist = whitelist; 093 } 094 095 /** 096 * @return the blacklist 097 */ 098 public Set<String> getBlacklist() { 099 return blacklist; 100 } 101 102 /** 103 * @param blacklist the blacklist to set 104 */ 105 public void setBlacklist(Set<String> blacklist) { 106 this.blacklist = blacklist; 107 } 108 109 @Override 110 public ServerConfiguration getServerConfiguration(String issuer) { 111 try { 112 113 if (!whitelist.isEmpty() && !whitelist.contains(issuer)) { 114 throw new AuthenticationServiceException("Whitelist was nonempty, issuer was not in whitelist: " + issuer); 115 } 116 117 if (blacklist.contains(issuer)) { 118 throw new AuthenticationServiceException("Issuer was in blacklist: " + issuer); 119 } 120 121 return servers.get(issuer); 122 } catch (UncheckedExecutionException | ExecutionException e) { 123 logger.warn("Couldn't load configuration for " + issuer + ": " + e); 124 return null; 125 } 126 127 } 128 129 /** 130 * @author jricher 131 * 132 */ 133 private class OpenIDConnectServiceConfigurationFetcher extends CacheLoader<String, ServerConfiguration> { 134 private HttpComponentsClientHttpRequestFactory httpFactory; 135 private JsonParser parser = new JsonParser(); 136 137 OpenIDConnectServiceConfigurationFetcher(HttpClient httpClient) { 138 this.httpFactory = new HttpComponentsClientHttpRequestFactory(httpClient); 139 } 140 141 @Override 142 public ServerConfiguration load(String issuer) throws Exception { 143 RestTemplate restTemplate = new RestTemplate(httpFactory); 144 145 // data holder 146 ServerConfiguration conf = new ServerConfiguration(); 147 148 // construct the well-known URI 149 String url = issuer + "/.well-known/openid-configuration"; 150 151 // fetch the value 152 String jsonString = restTemplate.getForObject(url, String.class); 153 154 JsonElement parsed = parser.parse(jsonString); 155 if (parsed.isJsonObject()) { 156 157 JsonObject o = parsed.getAsJsonObject(); 158 159 // sanity checks 160 if (!o.has("issuer")) { 161 throw new IllegalStateException("Returned object did not have an 'issuer' field"); 162 } 163 164 if (!issuer.equals(o.get("issuer").getAsString())) { 165 logger.info("Issuer used for discover was " + issuer + " but final issuer is " + o.get("issuer").getAsString()); 166 } 167 168 conf.setIssuer(o.get("issuer").getAsString()); 169 170 171 conf.setAuthorizationEndpointUri(getAsString(o, "authorization_endpoint")); 172 conf.setTokenEndpointUri(getAsString(o, "token_endpoint")); 173 conf.setJwksUri(getAsString(o, "jwks_uri")); 174 conf.setUserInfoUri(getAsString(o, "userinfo_endpoint")); 175 conf.setRegistrationEndpointUri(getAsString(o, "registration_endpoint")); 176 conf.setIntrospectionEndpointUri(getAsString(o, "introspection_endpoint")); 177 conf.setAcrValuesSupported(getAsStringList(o, "acr_values_supported")); 178 conf.setCheckSessionIframe(getAsString(o, "check_session_iframe")); 179 conf.setClaimsLocalesSupported(getAsStringList(o, "claims_locales_supported")); 180 conf.setClaimsParameterSupported(getAsBoolean(o, "claims_parameter_supported")); 181 conf.setClaimsSupported(getAsStringList(o, "claims_supported")); 182 conf.setDisplayValuesSupported(getAsStringList(o, "display_values_supported")); 183 conf.setEndSessionEndpoint(getAsString(o, "end_session_endpoint")); 184 conf.setGrantTypesSupported(getAsStringList(o, "grant_types_supported")); 185 conf.setIdTokenSigningAlgValuesSupported(getAsJwsAlgorithmList(o, "id_token_signing_alg_values_supported")); 186 conf.setIdTokenEncryptionAlgValuesSupported(getAsJweAlgorithmList(o, "id_token_encryption_alg_values_supported")); 187 conf.setIdTokenEncryptionEncValuesSupported(getAsEncryptionMethodList(o, "id_token_encryption_enc_values_supported")); 188 conf.setOpPolicyUri(getAsString(o, "op_policy_uri")); 189 conf.setOpTosUri(getAsString(o, "op_tos_uri")); 190 conf.setRequestObjectEncryptionAlgValuesSupported(getAsJweAlgorithmList(o, "request_object_encryption_alg_values_supported")); 191 conf.setRequestObjectEncryptionEncValuesSupported(getAsEncryptionMethodList(o, "request_object_encryption_enc_values_supported")); 192 conf.setRequestObjectSigningAlgValuesSupported(getAsJwsAlgorithmList(o, "request_object_signing_alg_values_supported")); 193 conf.setRequestParameterSupported(getAsBoolean(o, "request_parameter_supported")); 194 conf.setRequestUriParameterSupported(getAsBoolean(o, "request_uri_parameter_supported")); 195 conf.setResponseTypesSupported(getAsStringList(o, "response_types_supported")); 196 conf.setScopesSupported(getAsStringList(o, "scopes_supported")); 197 conf.setSubjectTypesSupported(getAsStringList(o, "subject_types_supported")); 198 conf.setServiceDocumentation(getAsString(o, "service_documentation")); 199 conf.setTokenEndpointAuthMethodsSupported(getAsStringList(o, "token_endpoint_auth_methods")); 200 conf.setTokenEndpointAuthSigningAlgValuesSupported(getAsJwsAlgorithmList(o, "token_endpoint_auth_signing_alg_values_supported")); 201 conf.setUiLocalesSupported(getAsStringList(o, "ui_locales_supported")); 202 conf.setUserinfoEncryptionAlgValuesSupported(getAsJweAlgorithmList(o, "userinfo_encryption_alg_values_supported")); 203 conf.setUserinfoEncryptionEncValuesSupported(getAsEncryptionMethodList(o, "userinfo_encryption_enc_values_supported")); 204 conf.setUserinfoSigningAlgValuesSupported(getAsJwsAlgorithmList(o, "userinfo_signing_alg_values_supported")); 205 206 return conf; 207 } else { 208 throw new IllegalStateException("Couldn't parse server discovery results for " + url); 209 } 210 211 } 212 213 } 214 215}