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 *******************************************************************************/ 018package org.mitre.discovery.web; 019 020import java.util.ArrayList; 021import java.util.Collection; 022import java.util.HashMap; 023import java.util.Map; 024 025import org.mitre.discovery.util.WebfingerURLNormalizer; 026import org.mitre.jwt.encryption.service.JWTEncryptionAndDecryptionService; 027import org.mitre.jwt.signer.service.JWTSigningAndValidationService; 028import org.mitre.oauth2.model.PKCEAlgorithm; 029import org.mitre.oauth2.service.SystemScopeService; 030import org.mitre.oauth2.web.DeviceEndpoint; 031import org.mitre.oauth2.web.IntrospectionEndpoint; 032import org.mitre.oauth2.web.RevocationEndpoint; 033import org.mitre.openid.connect.config.ConfigurationPropertiesBean; 034import org.mitre.openid.connect.model.UserInfo; 035import org.mitre.openid.connect.service.UserInfoService; 036import org.mitre.openid.connect.view.HttpCodeView; 037import org.mitre.openid.connect.view.JsonEntityView; 038import org.mitre.openid.connect.web.DynamicClientRegistrationEndpoint; 039import org.mitre.openid.connect.web.EndSessionEndpoint; 040import org.mitre.openid.connect.web.JWKSetPublishingEndpoint; 041import org.mitre.openid.connect.web.UserInfoEndpoint; 042import org.slf4j.Logger; 043import org.slf4j.LoggerFactory; 044import org.springframework.beans.factory.annotation.Autowired; 045import org.springframework.http.HttpStatus; 046import org.springframework.http.MediaType; 047import org.springframework.stereotype.Controller; 048import org.springframework.ui.Model; 049import org.springframework.web.bind.annotation.RequestMapping; 050import org.springframework.web.bind.annotation.RequestParam; 051import org.springframework.web.util.UriComponents; 052import org.springframework.web.util.UriComponentsBuilder; 053 054import com.google.common.base.Function; 055import com.google.common.base.Strings; 056import com.google.common.collect.Collections2; 057import com.google.common.collect.Lists; 058import com.nimbusds.jose.Algorithm; 059import com.nimbusds.jose.JWSAlgorithm; 060 061/** 062 * 063 * Handle OpenID Connect Discovery. 064 * 065 * @author jricher 066 * 067 */ 068@Controller 069public class DiscoveryEndpoint { 070 071 public static final String WELL_KNOWN_URL = ".well-known"; 072 public static final String OPENID_CONFIGURATION_URL = WELL_KNOWN_URL + "/openid-configuration"; 073 public static final String WEBFINGER_URL = WELL_KNOWN_URL + "/webfinger"; 074 075 /** 076 * Logger for this class 077 */ 078 private static final Logger logger = LoggerFactory.getLogger(DiscoveryEndpoint.class); 079 080 @Autowired 081 private ConfigurationPropertiesBean config; 082 083 @Autowired 084 private SystemScopeService scopeService; 085 086 @Autowired 087 private JWTSigningAndValidationService signService; 088 089 @Autowired 090 private JWTEncryptionAndDecryptionService encService; 091 092 @Autowired 093 private UserInfoService userService; 094 095 096 // used to map JWA algorithms objects to strings 097 private Function<Algorithm, String> toAlgorithmName = new Function<Algorithm, String>() { 098 @Override 099 public String apply(Algorithm alg) { 100 if (alg == null) { 101 return null; 102 } else { 103 return alg.getName(); 104 } 105 } 106 }; 107 108 @RequestMapping(value={"/" + WEBFINGER_URL}, produces = MediaType.APPLICATION_JSON_VALUE) 109 public String webfinger(@RequestParam("resource") String resource, @RequestParam(value = "rel", required = false) String rel, Model model) { 110 111 if (!Strings.isNullOrEmpty(rel) && !rel.equals("http://openid.net/specs/connect/1.0/issuer")) { 112 logger.warn("Responding to webfinger request for non-OIDC relation: " + rel); 113 } 114 115 if (!resource.equals(config.getIssuer())) { 116 // it's not the issuer directly, need to check other methods 117 118 UriComponents resourceUri = WebfingerURLNormalizer.normalizeResource(resource); 119 if (resourceUri != null 120 && resourceUri.getScheme() != null 121 && resourceUri.getScheme().equals("acct")) { 122 // acct: URI (email address format) 123 124 // check on email addresses first 125 UserInfo user = userService.getByEmailAddress(resourceUri.getUserInfo() + "@" + resourceUri.getHost()); 126 127 if (user == null) { 128 // user wasn't found, see if the local part of the username matches, plus our issuer host 129 130 user = userService.getByUsername(resourceUri.getUserInfo()); // first part is the username 131 132 if (user != null) { 133 // username matched, check the host component 134 UriComponents issuerComponents = UriComponentsBuilder.fromHttpUrl(config.getIssuer()).build(); 135 if (!Strings.nullToEmpty(issuerComponents.getHost()) 136 .equals(Strings.nullToEmpty(resourceUri.getHost()))) { 137 logger.info("Host mismatch, expected " + issuerComponents.getHost() + " got " + resourceUri.getHost()); 138 model.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); 139 return HttpCodeView.VIEWNAME; 140 } 141 142 } else { 143 144 // if the user's still null, punt and say we didn't find them 145 146 logger.info("User not found: " + resource); 147 model.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); 148 return HttpCodeView.VIEWNAME; 149 } 150 151 } 152 153 } else { 154 logger.info("Unknown URI format: " + resource); 155 model.addAttribute(HttpCodeView.CODE, HttpStatus.NOT_FOUND); 156 return HttpCodeView.VIEWNAME; 157 } 158 } 159 160 // if we got here, then we're good, return ourselves 161 model.addAttribute("resource", resource); 162 model.addAttribute("issuer", config.getIssuer()); 163 164 return "webfingerView"; 165 } 166 167 @RequestMapping("/" + OPENID_CONFIGURATION_URL) 168 public String providerConfiguration(Model model) { 169 170 /* 171 issuer 172 REQUIRED. URL using the https scheme with no query or fragment component that the OP asserts as its Issuer Identifier. 173 authorization_endpoint 174 OPTIONAL. URL of the OP's Authentication and Authorization Endpoint [OpenID.Messages]. 175 token_endpoint 176 OPTIONAL. URL of the OP's OAuth 2.0 Token Endpoint [OpenID.Messages]. 177 userinfo_endpoint 178 RECOMMENDED. URL of the OP's UserInfo Endpoint [OpenID.Messages]. This URL MUST use the https scheme 179 and MAY contain port, path, and query parameter components. 180 check_session_iframe 181 OPTIONAL. URL of an OP endpoint that provides a page to support cross-origin communications for session state information with 182 the RP Client, using the HTML5 postMessage API. The page is loaded from an invisible iframe embedded in an RP page so that 183 it can run in the OP's security context. See [OpenID.Session]. 184 end_session_endpoint 185 OPTIONAL. URL of the OP's endpoint that initiates logging out the End-User. See [OpenID.Session]. 186 jwks_uri 187 REQUIRED. URL of the OP's JSON Web Key Set [JWK] document. This contains the signing key(s) the Client uses to 188 validate signatures from the OP. The JWK Set MAY also contain the Server's encryption key(s), 189 which are used by Clients to encrypt requests to the Server. When both signing and encryption keys are made available, 190 a use (Key Use) parameter value is REQUIRED for all keys in the document to indicate each key's intended usage. 191 registration_endpoint 192 RECOMMENDED. URL of the OP's Dynamic Client Registration Endpoint [OpenID.Registration]. 193 scopes_supported 194 RECOMMENDED. JSON array containing a list of the OAuth 2.0 [RFC6749] scope values that this server supports. 195 The server MUST support the openid scope value. 196 response_types_supported 197 REQUIRED. JSON array containing a list of the OAuth 2.0 response_type values that this server supports. 198 The server MUST support the code, id_token, and the token id_token response type values. 199 grant_types_supported 200 OPTIONAL. JSON array containing a list of the OAuth 2.0 grant type values that this server supports. 201 The server MUST support the authorization_code and implicit grant type values 202 and MAY support the urn:ietf:params:oauth:grant-type:jwt-bearer grant type defined in OAuth JWT Bearer Token Profiles [OAuth.JWT]. 203 If omitted, the default value is ["authorization_code", "implicit"]. 204 acr_values_supported 205 OPTIONAL. JSON array containing a list of the Authentication Context Class References that this server supports. 206 subject_types_supported 207 REQUIRED. JSON array containing a list of the subject identifier types that this server supports. Valid types include pairwise and public. 208 userinfo_signing_alg_values_supported 209 OPTIONAL. JSON array containing a list of the JWS [JWS] signing algorithms (alg values) [JWA] supported by the UserInfo Endpoint to 210 encode the Claims in a JWT [JWT]. 211 userinfo_encryption_alg_values_supported 212 OPTIONAL. JSON array containing a list of the JWE [JWE] encryption algorithms (alg values) [JWA] supported by the UserInfo Endpoint to 213 encode the Claims in a JWT [JWT]. 214 userinfo_encryption_enc_values_supported 215 OPTIONAL. JSON array containing a list of the JWE encryption algorithms (enc values) [JWA] supported by the UserInfo Endpoint to 216 encode the Claims in a JWT [JWT]. 217 id_token_signing_alg_values_supported 218 REQUIRED. JSON array containing a list of the JWS signing algorithms (alg values) supported by the Authorization Server for the 219 ID Token to encode the Claims in a JWT [JWT]. 220 id_token_encryption_alg_values_supported 221 OPTIONAL. JSON array containing a list of the JWE encryption algorithms (alg values) supported by the Authorization Server for the 222 ID Token to encode the Claims in a JWT [JWT]. 223 id_token_encryption_enc_values_supported 224 OPTIONAL. JSON array containing a list of the JWE encryption algorithms (enc values) supported by the Authorization Server for the 225 ID Token to encode the Claims in a JWT [JWT]. 226 request_object_signing_alg_values_supported 227 OPTIONAL. JSON array containing a list of the JWS signing algorithms (alg values) supported by the Authorization Server for 228 the Request Object described in Section 2.9 of OpenID Connect Messages 1.0 [OpenID.Messages]. These algorithms are used both when 229 the Request Object is passed by value (using the request parameter) and when it is passed by reference (using the request_uri parameter). 230 Servers SHOULD support none and RS256. 231 request_object_encryption_alg_values_supported 232 OPTIONAL. JSON array containing a list of the JWE encryption algorithms (alg values) supported by the Authorization Server for 233 the Request Object described in Section 2.9 of OpenID Connect Messages 1.0 [OpenID.Messages]. These algorithms are used both when 234 the Request Object is passed by value and when it is passed by reference. 235 request_object_encryption_enc_values_supported 236 OPTIONAL. JSON array containing a list of the JWE encryption algorithms (enc values) supported by the Authorization Server for 237 the Request Object described in Section 2.9 of OpenID Connect Messages 1.0 [OpenID.Messages]. These algorithms are used both when 238 the Request Object is passed by value and when it is passed by reference. 239 token_endpoint_auth_methods_supported 240 OPTIONAL. JSON array containing a list of authentication methods supported by this Token Endpoint. 241 The options are client_secret_post, client_secret_basic, client_secret_jwt, and private_key_jwt, 242 as described in Section 2.2.1 of OpenID Connect Messages 1.0 [OpenID.Messages]. 243 Other authentication methods MAY be defined by extensions. 244 If omitted, the default is client_secret_basic -- the HTTP Basic Authentication Scheme as specified in 245 Section 2.3.1 of OAuth 2.0 [RFC6749]. 246 token_endpoint_auth_signing_alg_values_supported 247 OPTIONAL. JSON array containing a list of the JWS signing algorithms (alg values) supported by the Token Endpoint for 248 the private_key_jwt and client_secret_jwt methods to encode the JWT [JWT]. Servers SHOULD support RS256. 249 display_values_supported 250 OPTIONAL. JSON array containing a list of the display parameter values that the OpenID Provider supports. 251 These values are described in Section 2.1.1 of OpenID Connect Messages 1.0 [OpenID.Messages]. 252 claim_types_supported 253 OPTIONAL. JSON array containing a list of the Claim Types that the OpenID Provider supports. 254 These Claim Types are described in Section 2.6 of OpenID Connect Messages 1.0 [OpenID.Messages]. 255 Values defined by this specification are normal, aggregated, and distributed. 256 If not specified, the implementation supports only normal Claims. 257 claims_supported 258 RECOMMENDED. JSON array containing a list of the Claim Names of the Claims that the OpenID Provider MAY be able to supply values for. 259 Note that for privacy or other reasons, this might not be an exhaustive list. 260 service_documentation 261 OPTIONAL. URL of a page containing human-readable information that developers might want or need to know when using the OpenID Provider. 262 In particular, if the OpenID Provider does not support Dynamic Client Registration, then information on how to register Clients needs 263 to be provided in this documentation. 264 claims_locales_supported 265 OPTIONAL. Languages and scripts supported for values in Claims being returned, represented as a JSON array of 266 BCP47 [RFC5646] language tag values. Not all languages and scripts are necessarily supported for all Claim values. 267 ui_locales_supported 268 OPTIONAL. Languages and scripts supported for the user interface, represented as a JSON array of BCP47 [RFC5646] language tag values. 269 claims_parameter_supported 270 OPTIONAL. Boolean value specifying whether the OP supports use of the claims parameter, with true indicating support. 271 If omitted, the default value is false. 272 request_parameter_supported 273 OPTIONAL. Boolean value specifying whether the OP supports use of the request parameter, with true indicating support. 274 If omitted, the default value is false. 275 request_uri_parameter_supported 276 OPTIONAL. Boolean value specifying whether the OP supports use of the request_uri parameter, with true indicating support. 277 If omitted, the default value is true. 278 require_request_uri_registration 279 OPTIONAL. Boolean value specifying whether the OP requires any request_uri values used to be pre-registered using 280 the request_uris registration parameter. Pre-registration is REQUIRED when the value is true. If omitted, the default value is false. 281 op_policy_uri 282 OPTIONAL. URL that the OpenID Provider provides to the person registering the Client to read about the OP's requirements on 283 how the Relying Party can use the data provided by the OP. The registration process SHOULD display this URL to the person registering 284 the Client if it is given. 285 op_tos_uri 286 OPTIONAL. URL that the OpenID Provider provides to the person registering the Client to read about OpenID Provider's terms of service. 287 The registration process SHOULD display this URL to the person registering the Client if it is given. 288 */ 289 String baseUrl = config.getIssuer(); 290 291 if (!baseUrl.endsWith("/")) { 292 logger.debug("Configured issuer doesn't end in /, adding for discovery: {}", baseUrl); 293 baseUrl = baseUrl.concat("/"); 294 } 295 296 signService.getAllSigningAlgsSupported(); 297 Lists.newArrayList(JWSAlgorithm.HS256, JWSAlgorithm.HS384, JWSAlgorithm.HS512); 298 Collection<JWSAlgorithm> clientSymmetricAndAsymmetricSigningAlgs = Lists.newArrayList(JWSAlgorithm.HS256, JWSAlgorithm.HS384, JWSAlgorithm.HS512, 299 JWSAlgorithm.RS256, JWSAlgorithm.RS384, JWSAlgorithm.RS512, 300 JWSAlgorithm.ES256, JWSAlgorithm.ES384, JWSAlgorithm.ES512, 301 JWSAlgorithm.PS256, JWSAlgorithm.PS384, JWSAlgorithm.PS512); 302 Collection<Algorithm> clientSymmetricAndAsymmetricSigningAlgsWithNone = Lists.newArrayList(JWSAlgorithm.HS256, JWSAlgorithm.HS384, JWSAlgorithm.HS512, 303 JWSAlgorithm.RS256, JWSAlgorithm.RS384, JWSAlgorithm.RS512, 304 JWSAlgorithm.ES256, JWSAlgorithm.ES384, JWSAlgorithm.ES512, 305 JWSAlgorithm.PS256, JWSAlgorithm.PS384, JWSAlgorithm.PS512, 306 Algorithm.NONE); 307 ArrayList<String> grantTypes = Lists.newArrayList("authorization_code", "implicit", "urn:ietf:params:oauth:grant-type:jwt-bearer", "client_credentials", "urn:ietf:params:oauth:grant_type:redelegate", "urn:ietf:params:oauth:grant-type:device_code"); 308 309 Map<String, Object> m = new HashMap<>(); 310 m.put("issuer", config.getIssuer()); 311 m.put("authorization_endpoint", baseUrl + "authorize"); 312 m.put("token_endpoint", baseUrl + "token"); 313 m.put("userinfo_endpoint", baseUrl + UserInfoEndpoint.URL); 314 //check_session_iframe 315 m.put("end_session_endpoint", baseUrl + EndSessionEndpoint.URL); 316 m.put("jwks_uri", baseUrl + JWKSetPublishingEndpoint.URL); 317 m.put("registration_endpoint", baseUrl + DynamicClientRegistrationEndpoint.URL); 318 m.put("scopes_supported", scopeService.toStrings(scopeService.getUnrestricted())); // these are the scopes that you can dynamically register for, which is what matters for discovery 319 m.put("response_types_supported", Lists.newArrayList("code", "token")); // we don't support these yet: , "id_token", "id_token token")); 320 m.put("grant_types_supported", grantTypes); 321 //acr_values_supported 322 m.put("subject_types_supported", Lists.newArrayList("public", "pairwise")); 323 m.put("userinfo_signing_alg_values_supported", Collections2.transform(clientSymmetricAndAsymmetricSigningAlgs, toAlgorithmName)); 324 m.put("userinfo_encryption_alg_values_supported", Collections2.transform(encService.getAllEncryptionAlgsSupported(), toAlgorithmName)); 325 m.put("userinfo_encryption_enc_values_supported", Collections2.transform(encService.getAllEncryptionEncsSupported(), toAlgorithmName)); 326 m.put("id_token_signing_alg_values_supported", Collections2.transform(clientSymmetricAndAsymmetricSigningAlgsWithNone, toAlgorithmName)); 327 m.put("id_token_encryption_alg_values_supported", Collections2.transform(encService.getAllEncryptionAlgsSupported(), toAlgorithmName)); 328 m.put("id_token_encryption_enc_values_supported", Collections2.transform(encService.getAllEncryptionEncsSupported(), toAlgorithmName)); 329 m.put("request_object_signing_alg_values_supported", Collections2.transform(clientSymmetricAndAsymmetricSigningAlgs, toAlgorithmName)); 330 m.put("request_object_encryption_alg_values_supported", Collections2.transform(encService.getAllEncryptionAlgsSupported(), toAlgorithmName)); 331 m.put("request_object_encryption_enc_values_supported", Collections2.transform(encService.getAllEncryptionEncsSupported(), toAlgorithmName)); 332 m.put("token_endpoint_auth_methods_supported", Lists.newArrayList("client_secret_post", "client_secret_basic", "client_secret_jwt", "private_key_jwt", "none")); 333 m.put("token_endpoint_auth_signing_alg_values_supported", Collections2.transform(clientSymmetricAndAsymmetricSigningAlgs, toAlgorithmName)); 334 //display_types_supported 335 m.put("claim_types_supported", Lists.newArrayList("normal" /*, "aggregated", "distributed"*/)); 336 m.put("claims_supported", Lists.newArrayList( 337 "sub", 338 "name", 339 "preferred_username", 340 "given_name", 341 "family_name", 342 "middle_name", 343 "nickname", 344 "profile", 345 "picture", 346 "website", 347 "gender", 348 "zoneinfo", 349 "locale", 350 "updated_at", 351 "birthdate", 352 "email", 353 "email_verified", 354 "phone_number", 355 "phone_number_verified", 356 "address" 357 )); 358 m.put("service_documentation", baseUrl + "about"); 359 //claims_locales_supported 360 //ui_locales_supported 361 m.put("claims_parameter_supported", false); 362 m.put("request_parameter_supported", true); 363 m.put("request_uri_parameter_supported", false); 364 m.put("require_request_uri_registration", false); 365 m.put("op_policy_uri", baseUrl + "about"); 366 m.put("op_tos_uri", baseUrl + "about"); 367 368 m.put("introspection_endpoint", baseUrl + IntrospectionEndpoint.URL); // token introspection endpoint for verifying tokens 369 m.put("revocation_endpoint", baseUrl + RevocationEndpoint.URL); // token revocation endpoint 370 371 m.put("code_challenge_methods_supported", Lists.newArrayList(PKCEAlgorithm.plain.getName(), PKCEAlgorithm.S256.getName())); 372 373 m.put("device_authorization_endpoint", baseUrl + DeviceEndpoint.URL); 374 375 model.addAttribute(JsonEntityView.ENTITY, m); 376 377 return JsonEntityView.VIEWNAME; 378 } 379 380}