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