/*
* ******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2017] Pivotal Software, Inc. All Rights Reserved.
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product includes a number of subcomponents with
* separate copyright notices and license terms. Your use of these
* subcomponents is subject to the terms and conditions of the
* subcomponent's license, as noted in the LICENSE file.
* *******************************************************************************
*/
package org.cloudfoundry.identity.uaa.provider.oauth;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.cache.UrlContentCache;
import org.cloudfoundry.identity.uaa.provider.AbstractXOAuthIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.provider.IdentityProvider;
import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning;
import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.util.RestTemplateFactory;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OAUTH20;
import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OIDC10;
public class XOAuthProviderConfigurator implements IdentityProviderProvisioning {
private static Log log = LogFactory.getLog(XOAuthProviderConfigurator.class);
private final IdentityProviderProvisioning providerProvisioning;
private final UrlContentCache contentCache;
private final RestTemplateFactory restTemplateFactory;
public XOAuthProviderConfigurator(IdentityProviderProvisioning providerProvisioning,
UrlContentCache contentCache,
RestTemplateFactory restTemplateFactory) {
this.providerProvisioning = providerProvisioning;
this.contentCache = contentCache;
this.restTemplateFactory = restTemplateFactory;
}
protected OIDCIdentityProviderDefinition overlay(OIDCIdentityProviderDefinition definition) {
if (definition.getDiscoveryUrl() == null) {
return definition;
}
byte[] oidcJson = contentCache.getUrlContent(definition.getDiscoveryUrl().toString(), restTemplateFactory.getRestTemplate(definition.isSkipSslValidation()));
Map<String,Object> oidcConfig = JsonUtils.readValue(oidcJson, new TypeReference<Map<String, Object>>() {});
OIDCIdentityProviderDefinition overlayedDefinition = null;
try {
overlayedDefinition = (OIDCIdentityProviderDefinition) definition.clone();
URL authorizationEndpoint = new URL((String) oidcConfig.get("authorization_endpoint"));
URL userinfoEndpoint = new URL((String) oidcConfig.get("userinfo_endpoint"));
URL tokenEndpoint = new URL((String) oidcConfig.get("token_endpoint"));
URL tokenKeyUrl = new URL((String) oidcConfig.get("jwks_uri"));
String issuer = (String) oidcConfig.get("issuer");
overlayedDefinition.setAuthUrl(ofNullable(overlayedDefinition.getAuthUrl()).orElse(authorizationEndpoint));
overlayedDefinition.setUserInfoUrl(ofNullable(overlayedDefinition.getUserInfoUrl()).orElse(userinfoEndpoint));
overlayedDefinition.setTokenUrl(ofNullable(overlayedDefinition.getTokenUrl()).orElse(tokenEndpoint));
overlayedDefinition.setIssuer(ofNullable(overlayedDefinition.getIssuer()).orElse(issuer));
overlayedDefinition.setTokenKeyUrl(ofNullable(overlayedDefinition.getTokenKeyUrl()).orElse(tokenKeyUrl));
} catch (MalformedURLException e) {
throw new IllegalStateException(e);
} catch (CloneNotSupportedException e) {
throw new IllegalStateException(e);
}
return overlayedDefinition;
}
public String getCompleteAuthorizationURI(String alias, String baseURL, AbstractXOAuthIdentityProviderDefinition definition) {
try {
String authUrlBase = definition.getAuthUrl().toString();
String queryAppendDelimiter = authUrlBase.contains("?") ? "&" : "?";
List<String> query = new ArrayList<>();
query.add("client_id=" + definition.getRelyingPartyId());
query.add("response_type="+ URLEncoder.encode(definition.getResponseType(), "UTF-8"));
query.add("redirect_uri=" + URLEncoder.encode(baseURL + "/login/callback/" + alias, "UTF-8"));
if (definition.getScopes() != null && !definition.getScopes().isEmpty()) {
query.add("scope=" + URLEncoder.encode(String.join(" ", definition.getScopes()), "UTF-8"));
}
if (OIDCIdentityProviderDefinition.class.equals(definition.getParameterizedClass())) {
final RandomValueStringGenerator nonceGenerator = new RandomValueStringGenerator(12);
query.add("nonce=" + nonceGenerator.generate());
}
String queryString = String.join("&", query);
return authUrlBase + queryAppendDelimiter + queryString;
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
@Override
public IdentityProvider create(IdentityProvider identityProvider) {
return providerProvisioning.create(identityProvider);
}
@Override
public IdentityProvider update(IdentityProvider identityProvider) {
return providerProvisioning.update(identityProvider);
}
@Override
public IdentityProvider retrieve(String id) {
IdentityProvider p = providerProvisioning.retrieve(id);
if (p!=null && p.getType().equals(OIDC10)) {
p.setConfig(overlay((OIDCIdentityProviderDefinition) p.getConfig()));
}
return p;
}
@Override
public List<IdentityProvider> retrieveActive(String zoneId) {
return retrieveAll(true, zoneId);
}
@Override
public List<IdentityProvider> retrieveAll(boolean activeOnly, String zoneId) {
final List<String> types = Arrays.asList(OAUTH20, OIDC10);
List<IdentityProvider> providers = providerProvisioning.retrieveAll(activeOnly, zoneId);
List<IdentityProvider> overlayedProviders = new ArrayList<>();
ofNullable(providers).orElse(emptyList()).stream()
.filter(p -> types.contains(p.getType()))
.forEach(p -> {
if (p.getType().equals(OIDC10)) {
try {
OIDCIdentityProviderDefinition overlayedDefinition = overlay((OIDCIdentityProviderDefinition) p.getConfig());
p.setConfig(overlayedDefinition);
} catch (Exception e) {
log.error("Identity provider excluded from login page due to a problem.", e);
return;
}
}
overlayedProviders.add(p);
});
return overlayedProviders;
}
@Override
public IdentityProvider retrieveByOrigin(String origin, String zoneId) {
IdentityProvider p = providerProvisioning.retrieveByOrigin(origin, zoneId);
if (p!=null && p.getType().equals(OIDC10)) {
p.setConfig(overlay((OIDCIdentityProviderDefinition) p.getConfig()));
}
return p;
}
}