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}