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