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