001/*
002 * oauth2-oidc-sdk
003 *
004 * Copyright 2012-2020, 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.federation.trust;
019
020
021import java.io.IOException;
022import java.net.URI;
023
024import com.nimbusds.oauth2.sdk.ErrorObject;
025import com.nimbusds.oauth2.sdk.ParseException;
026import com.nimbusds.oauth2.sdk.WellKnownPathComposeStrategy;
027import com.nimbusds.oauth2.sdk.http.HTTPRequest;
028import com.nimbusds.oauth2.sdk.http.HTTPResponse;
029import com.nimbusds.oauth2.sdk.util.StringUtils;
030import com.nimbusds.openid.connect.sdk.federation.api.FetchEntityStatementRequest;
031import com.nimbusds.openid.connect.sdk.federation.api.FetchEntityStatementResponse;
032import com.nimbusds.openid.connect.sdk.federation.config.FederationEntityConfigurationRequest;
033import com.nimbusds.openid.connect.sdk.federation.config.FederationEntityConfigurationResponse;
034import com.nimbusds.openid.connect.sdk.federation.entities.EntityID;
035import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
036
037
038/**
039 * The default entity statement retriever for resolving trust chains. Supports
040 * the {@link WellKnownPathComposeStrategy#POSTFIX postfix} and
041 * {@link WellKnownPathComposeStrategy#INFIX infix} well-known path composition
042 * strategies.
043 */
044public class DefaultEntityStatementRetriever implements EntityStatementRetriever {
045        
046        
047        /**
048         * The HTTP connect timeout in milliseconds.
049         */
050        private final int httpConnectTimeoutMs;
051        
052        
053        /**
054         * The HTTP read timeout in milliseconds.
055         */
056        private final int httpReadTimeoutMs;
057        
058        
059        /**
060         * The default HTTP connect timeout in milliseconds.
061         */
062        public static final int DEFAULT_HTTP_CONNECT_TIMEOUT_MS = 1000;
063        
064        
065        /**
066         * The default HTTP read timeout in milliseconds.
067         */
068        public static final int DEFAULT_HTTP_READ_TIMEOUT_MS = 1000;
069        
070        
071        /**
072         * Creates a new entity statement retriever using the default HTTP
073         * timeout settings.
074         */
075        public DefaultEntityStatementRetriever() {
076                this(DEFAULT_HTTP_CONNECT_TIMEOUT_MS, DEFAULT_HTTP_READ_TIMEOUT_MS);
077        }
078        
079        
080        /**
081         * Creates a new entity statement retriever.
082         *
083         * @param httpConnectTimeoutMs The HTTP connect timeout in
084         *                             milliseconds, zero means timeout
085         *                             determined by the underlying HTTP client.
086         * @param httpReadTimeoutMs    The HTTP read timeout in milliseconds,
087         *                             zero means timeout determined by the
088         *                             underlying HTTP client.
089         */
090        public DefaultEntityStatementRetriever(final int httpConnectTimeoutMs,
091                                               final int httpReadTimeoutMs) {
092                this.httpConnectTimeoutMs = httpConnectTimeoutMs;
093                this.httpReadTimeoutMs = httpReadTimeoutMs;
094        }
095        
096        
097        /**
098         * Returns the configured HTTP connect timeout.
099         *
100         * @return The configured HTTP connect timeout in milliseconds, zero
101         *         means timeout determined by the underlying HTTP client.
102         */
103        public int getHTTPConnectTimeout() {
104                return httpConnectTimeoutMs;
105        }
106        
107        
108        /**
109         * Returns the configured HTTP read timeout.
110         *
111         * @return The configured HTTP read timeout in milliseconds, zero
112         *         means timeout determined by the underlying HTTP client.
113         */
114        public int getHTTPReadTimeout() {
115                return httpReadTimeoutMs;
116        }
117        
118        
119        void applyTimeouts(final HTTPRequest httpRequest) {
120                httpRequest.setConnectTimeout(httpConnectTimeoutMs);
121                httpRequest.setReadTimeout(httpReadTimeoutMs);
122        }
123        
124        
125        @Override
126        public EntityStatement fetchSelfIssuedEntityStatement(final EntityID target)
127                throws ResolveException {
128                
129                FederationEntityConfigurationRequest request = new FederationEntityConfigurationRequest(target);
130                HTTPRequest httpRequest = request.toHTTPRequest();
131                applyTimeouts(httpRequest);
132                
133                HTTPResponse httpResponse;
134                try {
135                        httpResponse = httpRequest.send();
136                } catch (IOException e) {
137                        throw new ResolveException("Couldn't retrieve entity configuration for " + httpRequest.getURL() + ": " + e.getMessage(), e);
138                }
139                
140                if (StringUtils.isNotBlank(target.toURI().getPath()) && HTTPResponse.SC_NOT_FOUND == httpResponse.getStatusCode()) {
141                        // We have a path in the entity ID URL, try infix strategy
142                        request = new FederationEntityConfigurationRequest(target, WellKnownPathComposeStrategy.INFIX);
143                        httpRequest = request.toHTTPRequest();
144                        applyTimeouts(httpRequest);
145                        
146                        try {
147                                httpResponse = httpRequest.send();
148                        } catch (IOException e) {
149                                throw new ResolveException("Couldn't retrieve entity configuration for " + httpRequest.getURL() + ": " + e.getMessage(), e);
150                        }
151                }
152                
153                FederationEntityConfigurationResponse response;
154                try {
155                        response = FederationEntityConfigurationResponse.parse(httpResponse);
156                } catch (ParseException e) {
157                        throw new ResolveException("Error parsing entity configuration response from " + httpRequest.getURL() + ": " + e.getMessage(), e);
158                }
159                
160                if (! response.indicatesSuccess()) {
161                        ErrorObject errorObject = response.toErrorResponse().getErrorObject();
162                        throw new ResolveException("Entity configuration error response from " + httpRequest.getURL() + ": " +
163                                errorObject.getHTTPStatusCode() +
164                                (errorObject.getCode() != null ? " " + errorObject.getCode() : ""),
165                                errorObject);
166                }
167                
168                return response.toSuccessResponse().getEntityStatement();
169        }
170        
171        
172        @Override
173        public EntityStatement fetchEntityStatement(final URI federationAPIEndpoint, final EntityID issuer, final EntityID subject)
174                throws ResolveException {
175                
176                FetchEntityStatementRequest request = new FetchEntityStatementRequest(federationAPIEndpoint, issuer, subject, null);
177                HTTPRequest httpRequest = request.toHTTPRequest();
178                applyTimeouts(httpRequest);
179                
180                HTTPResponse httpResponse;
181                try {
182                        httpResponse = httpRequest.send();
183                } catch (IOException e) {
184                        throw new ResolveException("Couldn't fetch entity statement from " + issuer + " at " + federationAPIEndpoint + ": " + e.getMessage(), e);
185                }
186                
187                FetchEntityStatementResponse response;
188                try {
189                        response = FetchEntityStatementResponse.parse(httpResponse);
190                } catch (ParseException e) {
191                        throw new ResolveException("Error parsing entity statement response from " + issuer + " at " + federationAPIEndpoint + ": " + e.getMessage(), e);
192                }
193                
194                if (! response.indicatesSuccess()) {
195                        ErrorObject errorObject = response.toErrorResponse().getErrorObject();
196                        throw new ResolveException("Entity statement error response from " + issuer + " at " + federationAPIEndpoint + ": " +
197                                errorObject.getHTTPStatusCode() +
198                                (errorObject.getCode() != null ? " " + errorObject.getCode() : ""),
199                                errorObject);
200                }
201                
202                return response.toSuccessResponse().getEntityStatement();
203        }
204}