better handling of HTTP and JSON errors on network fetches, added http-forcing behavior for webfinger client and sector URL service

pull/820/merge
Justin Richer 2015-06-23 22:21:18 -04:00
parent 9ae92b983a
commit f4a1b27e2e
6 changed files with 84 additions and 32 deletions

View File

@ -29,7 +29,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import com.google.common.collect.Iterables;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClientBuilder;
@ -57,10 +56,11 @@ import org.springframework.security.web.authentication.AbstractAuthenticationPro
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
@ -407,15 +407,13 @@ public class OIDCAuthenticationFilter extends AbstractAuthenticationProcessingFi
try { try {
jsonString = restTemplate.postForObject(serverConfig.getTokenEndpointUri(), form, String.class); jsonString = restTemplate.postForObject(serverConfig.getTokenEndpointUri(), form, String.class);
} catch (HttpClientErrorException httpClientErrorException) { } catch (RestClientException e) {
// Handle error // Handle error
logger.error("Token Endpoint error response: " logger.error("Token Endpoint error response: " + e.getMessage());
+ httpClientErrorException.getStatusText() + " : "
+ httpClientErrorException.getMessage());
throw new AuthenticationServiceException("Unable to obtain Access Token: " + httpClientErrorException.getMessage()); throw new AuthenticationServiceException("Unable to obtain Access Token: " + e.getMessage());
} }
logger.debug("from TokenEndpoint jsonString = " + jsonString); logger.debug("from TokenEndpoint jsonString = " + jsonString);

View File

@ -114,11 +114,8 @@ public class DynamicServerConfigurationService implements ServerConfigurationSer
} }
return servers.get(issuer); return servers.get(issuer);
} catch (UncheckedExecutionException ue) { } catch (UncheckedExecutionException | ExecutionException e) {
logger.warn("Couldn't load configuration for " + issuer, ue); logger.warn("Couldn't load configuration for " + issuer + ": " + e);
return null;
} catch (ExecutionException e) {
logger.warn("Couldn't load configuration for " + issuer, e);
return null; return null;
} }

View File

@ -36,6 +36,38 @@ import com.google.common.collect.Sets;
*/ */
public class HybridIssuerService implements IssuerService { public class HybridIssuerService implements IssuerService {
/**
* @return
* @see org.mitre.openid.connect.client.service.impl.ThirdPartyIssuerService#getAccountChooserUrl()
*/
public String getAccountChooserUrl() {
return thirdPartyIssuerService.getAccountChooserUrl();
}
/**
* @param accountChooserUrl
* @see org.mitre.openid.connect.client.service.impl.ThirdPartyIssuerService#setAccountChooserUrl(java.lang.String)
*/
public void setAccountChooserUrl(String accountChooserUrl) {
thirdPartyIssuerService.setAccountChooserUrl(accountChooserUrl);
}
/**
* @return
* @see org.mitre.openid.connect.client.service.impl.WebfingerIssuerService#isForceHttps()
*/
public boolean isForceHttps() {
return webfingerIssuerService.isForceHttps();
}
/**
* @param forceHttps
* @see org.mitre.openid.connect.client.service.impl.WebfingerIssuerService#setForceHttps(boolean)
*/
public void setForceHttps(boolean forceHttps) {
webfingerIssuerService.setForceHttps(forceHttps);
}
private ThirdPartyIssuerService thirdPartyIssuerService = new ThirdPartyIssuerService(); private ThirdPartyIssuerService thirdPartyIssuerService = new ThirdPartyIssuerService();
private WebfingerIssuerService webfingerIssuerService = new WebfingerIssuerService(); private WebfingerIssuerService webfingerIssuerService = new WebfingerIssuerService();

View File

@ -78,6 +78,11 @@ public class WebfingerIssuerService implements IssuerService {
*/ */
private String loginPageUrl; private String loginPageUrl;
/**
* Strict enfocement of "https"
*/
private boolean forceHttps = true;
public WebfingerIssuerService() { public WebfingerIssuerService() {
issuers = CacheBuilder.newBuilder().build(new WebfingerIssuerFetcher()); issuers = CacheBuilder.newBuilder().build(new WebfingerIssuerFetcher());
} }
@ -102,7 +107,7 @@ public class WebfingerIssuerService implements IssuerService {
return new IssuerServiceResponse(issuer, identifier, null); return new IssuerServiceResponse(issuer, identifier, null);
} catch (UncheckedExecutionException | ExecutionException e) { } catch (UncheckedExecutionException | ExecutionException e) {
logger.warn("Issue fetching issuer for user input: " + identifier, e.getMessage()); logger.warn("Issue fetching issuer for user input: " + identifier + ": " + e.getMessage());
return null; return null;
} }
@ -169,6 +174,20 @@ public class WebfingerIssuerService implements IssuerService {
this.blacklist = blacklist; this.blacklist = blacklist;
} }
/**
* @return the forceHttps
*/
public boolean isForceHttps() {
return forceHttps;
}
/**
* @param forceHttps the forceHttps to set
*/
public void setForceHttps(boolean forceHttps) {
this.forceHttps = forceHttps;
}
/** /**
* @author jricher * @author jricher
* *
@ -188,9 +207,16 @@ public class WebfingerIssuerService implements IssuerService {
// preserving http scheme is strictly for demo system use only. // preserving http scheme is strictly for demo system use only.
String scheme = key.getScheme(); String scheme = key.getScheme();
if (!Strings.isNullOrEmpty(scheme) && scheme.equals("http")) {
if (!Strings.isNullOrEmpty(scheme)) {
if (scheme.equals("http")) {
if (forceHttps) {
throw new IllegalArgumentException("Scheme must start with htps");
} else {
logger.warn("Webfinger endpoint MUST use the https URI scheme, overriding by configuration");
scheme = "http://"; // add on colon and slashes. scheme = "http://"; // add on colon and slashes.
logger.warn("Webfinger endpoint MUST use the https URI scheme."); }
}
} else { } else {
scheme = "https://"; scheme = "https://";
} }

View File

@ -82,11 +82,8 @@ public class JWKSetCacheService {
public JWTSigningAndValidationService getValidator(String jwksUri) { public JWTSigningAndValidationService getValidator(String jwksUri) {
try { try {
return validators.get(jwksUri); return validators.get(jwksUri);
} catch (UncheckedExecutionException ue) { } catch (UncheckedExecutionException | ExecutionException e) {
logger.warn("Couldn't load JWK Set from " + jwksUri, ue); logger.warn("Couldn't load JWK Set from " + jwksUri + ": " + e.getMessage());
return null;
} catch (ExecutionException e) {
logger.warn("Couldn't load JWK Set from " + jwksUri, e);
return null; return null;
} }
} }
@ -94,11 +91,8 @@ public class JWKSetCacheService {
public JWTEncryptionAndDecryptionService getEncrypter(String jwksUri) { public JWTEncryptionAndDecryptionService getEncrypter(String jwksUri) {
try { try {
return encrypters.get(jwksUri); return encrypters.get(jwksUri);
} catch (UncheckedExecutionException ue) { } catch (UncheckedExecutionException | ExecutionException e) {
logger.warn("Couldn't load JWK Set from " + jwksUri, ue); logger.warn("Couldn't load JWK Set from " + jwksUri + ": " + e.getMessage());
return null;
} catch (ExecutionException e) {
logger.warn("Couldn't load JWK Set from " + jwksUri, e);
return null; return null;
} }
} }
@ -117,7 +111,6 @@ public class JWKSetCacheService {
*/ */
@Override @Override
public JWTSigningAndValidationService load(String key) throws Exception { public JWTSigningAndValidationService load(String key) throws Exception {
String jsonString = restTemplate.getForObject(key, String.class); String jsonString = restTemplate.getForObject(key, String.class);
JWKSet jwkSet = JWKSet.parse(jsonString); JWKSet jwkSet = JWKSet.parse(jsonString);
@ -126,7 +119,6 @@ public class JWKSetCacheService {
JWTSigningAndValidationService service = new DefaultJWTSigningAndValidationService(keyStore); JWTSigningAndValidationService service = new DefaultJWTSigningAndValidationService(keyStore);
return service; return service;
} }
} }

View File

@ -36,6 +36,7 @@ import org.mitre.oauth2.repository.OAuth2ClientRepository;
import org.mitre.oauth2.repository.OAuth2TokenRepository; import org.mitre.oauth2.repository.OAuth2TokenRepository;
import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.oauth2.service.ClientDetailsEntityService;
import org.mitre.oauth2.service.SystemScopeService; import org.mitre.oauth2.service.SystemScopeService;
import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
import org.mitre.openid.connect.model.WhitelistedSite; import org.mitre.openid.connect.model.WhitelistedSite;
import org.mitre.openid.connect.service.ApprovedSiteService; import org.mitre.openid.connect.service.ApprovedSiteService;
import org.mitre.openid.connect.service.BlacklistedSiteService; import org.mitre.openid.connect.service.BlacklistedSiteService;
@ -54,6 +55,7 @@ import com.google.common.base.Strings;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.UncheckedExecutionException;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
@ -86,6 +88,9 @@ public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEnt
@Autowired @Autowired
private StatsService statsService; private StatsService statsService;
@Autowired
private ConfigurationPropertiesBean config;
// map of sector URI -> list of redirect URIs // map of sector URI -> list of redirect URIs
private LoadingCache<String, List<String>> sectorRedirects = CacheBuilder.newBuilder() private LoadingCache<String, List<String>> sectorRedirects = CacheBuilder.newBuilder()
.expireAfterAccess(1, TimeUnit.HOURS) .expireAfterAccess(1, TimeUnit.HOURS)
@ -167,8 +172,8 @@ public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEnt
} }
} }
} catch (ExecutionException e) { } catch (UncheckedExecutionException | ExecutionException e) {
throw new IllegalArgumentException("Unable to load sector identifier URI: " + client.getSectorIdentifierUri()); throw new IllegalArgumentException("Unable to load sector identifier URI " + client.getSectorIdentifierUri() + ": " + e.getMessage());
} }
} }
} }
@ -321,7 +326,9 @@ public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEnt
public List<String> load(String key) throws Exception { public List<String> load(String key) throws Exception {
if (!key.startsWith("https")) { if (!key.startsWith("https")) {
// TODO: this should optionally throw an error (#506) if (config.isForceHttps()) {
throw new IllegalArgumentException("Sector identifier must start with https: " + key);
}
logger.error("Sector identifier doesn't start with https, loading anyway..."); logger.error("Sector identifier doesn't start with https, loading anyway...");
} }
@ -339,7 +346,7 @@ public class DefaultOAuth2ClientDetailsEntityService implements ClientDetailsEnt
return redirectUris; return redirectUris;
} else { } else {
return null; throw new IllegalArgumentException("JSON Format Error");
} }
} }