001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2016, Connect2id Ltd and contributors.
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
007 * this file except in compliance with the License. You may obtain a copy of the
008 * 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 distributed
013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the
015 * specific language governing permissions and limitations under the License.
016 */
017
018package com.nimbusds.openid.connect.sdk.id;
019
020
021import com.nimbusds.jose.util.Resource;
022import com.nimbusds.jose.util.ResourceRetriever;
023import com.nimbusds.oauth2.sdk.GeneralException;
024import com.nimbusds.oauth2.sdk.ciba.BackChannelTokenDeliveryMode;
025import com.nimbusds.oauth2.sdk.util.JSONArrayUtils;
026import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
027
028import java.io.IOException;
029import java.net.URI;
030import java.util.*;
031
032
033/**
034 * Sector identifier URI validator.
035 *
036 * <p>Related specifications:
037 *
038 * <ul>
039 *     <li>OpenID Connect Core 1.0
040 *     <li>OpenID Connect Dynamic Client Registration 1.0
041 *     <li>OpenID Connect CIBA Flow - Core 1.0
042 * </ul>
043 */
044public class SectorIDURIValidator {
045        
046
047        /**
048         * The URL resource retriever.
049         */
050        private final ResourceRetriever resourceRetriever;
051
052
053        /**
054         * Creates a new sector ID URI validator.
055         *
056         * @param resourceRetriever The URL resource retriever to use. Must not
057         *                          be {@code null}.
058         */
059        public SectorIDURIValidator(final ResourceRetriever resourceRetriever) {
060                this.resourceRetriever = Objects.requireNonNull(resourceRetriever);
061        }
062
063
064        /**
065         * Returns the URL resource retriever.
066         *
067         * @return The resource retriever.
068         */
069        public ResourceRetriever getResourceRetriever() {
070                return resourceRetriever;
071        }
072
073
074        /**
075         * Validates the specified URIs for being present in a sector ID
076         * document.
077         *
078         * @param sectorURI      The sector ID URI. Must not be {@code null}.
079         * @param urisToValidate The client URIs to check for being present in
080         *                       the sector ID JSON document. Must not be
081         *                       {@code null}.
082         *
083         * @throws GeneralException If validation failed.
084         */
085        public void validate(final URI sectorURI, final Set<URI> urisToValidate)
086                throws GeneralException {
087
088                Resource resource;
089                try {
090                        resource = resourceRetriever.retrieveResource(sectorURI.toURL());
091                } catch (IOException e) {
092                        throw new GeneralException("Couldn't retrieve the sector ID JSON document: " + e.getMessage(), e);
093                }
094
095                if (resource.getContentType() == null) {
096                        throw new GeneralException("Couldn't validate sector ID: Missing HTTP Content-Type");
097                }
098
099                if (! resource.getContentType().toLowerCase().startsWith("application/json")) {
100                        throw new GeneralException("Couldn't validate sector ID: HTTP Content-Type must be application/json, found " + resource.getContentType());
101                }
102
103                List<URI> uriList = JSONArrayUtils.toURIList(JSONArrayUtils.parse(resource.getContent()));
104
105                for (URI uri: urisToValidate) {
106
107                        if (! uriList.contains(uri)) {
108                                throw new GeneralException("Sector ID validation failed: URI " + uri + " not present at sector ID URI " + sectorURI);
109                        }
110                }
111        }
112        
113        
114        /**
115         * Collects the client URIs for sector ID validation.
116         *
117         * <p>For the OAuth 2.0 authorisation code and implicit grants:
118         * {@code redirect_uris}.
119         *
120         * <p>For the OAuth 2.0 CIBA grant: {@code jwks_uri} for the poll and
121         * ping token delivery modes,
122         * {@code backchannel_client_notification_endpoint} for the push mode.
123         *
124         * @param clientMetadata The client metadata. Must not be {@code null}.
125         *
126         * @return The URIs for sector ID validation, empty set if none.
127         */
128        public static Set<URI> collectURIsForValidation(final OIDCClientMetadata clientMetadata) {
129                
130                Set<URI> uris = new HashSet<>();
131                
132                // Grant types code, implicit
133                if (clientMetadata.getRedirectionURIs() != null) {
134                        uris.addAll(clientMetadata.getRedirectionURIs());
135                }
136                
137                // Grant type CIBA
138                if (BackChannelTokenDeliveryMode.POLL.equals(clientMetadata.getBackChannelTokenDeliveryMode()) ||
139                    BackChannelTokenDeliveryMode.PING.equals(clientMetadata.getBackChannelTokenDeliveryMode())) {
140                
141                        if (clientMetadata.getJWKSetURI() != null) {
142                                uris.add(clientMetadata.getJWKSetURI());
143                        }
144                }
145                if (BackChannelTokenDeliveryMode.PUSH.equals(clientMetadata.getBackChannelTokenDeliveryMode())) {
146                        
147                        if (clientMetadata.getBackChannelClientNotificationEndpoint() != null) {
148                                uris.add(clientMetadata.getBackChannelClientNotificationEndpoint());
149                        }
150                }
151                
152                return Collections.unmodifiableSet(uris);
153        }
154}