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}