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}