Skip to content

Commit

Permalink
Extract authorization url for OAuth providers into XOAuthProviderConf…
Browse files Browse the repository at this point in the history
…igurator

[#137497549] https://www.pivotaltracker.com/story/show/137497549

Signed-off-by: Filip Hanik <fhanik@pivotal.io>
  • Loading branch information
jhamon authored and fhanik committed Jan 23, 2017
1 parent 7191b2e commit c2ad4cf
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 40 deletions.
Expand Up @@ -15,13 +15,9 @@

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.ParameterizedType;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

@JsonIgnoreProperties(ignoreUnknown = true)
Expand Down Expand Up @@ -153,23 +149,4 @@ public Class getParameterizedClass() {
(ParameterizedType)getClass().getGenericSuperclass();
return (Class) parameterizedType.getActualTypeArguments()[0];
}

@JsonIgnore
public String getCompleteAuthorizationURI(String baseURL, String alias) throws UnsupportedEncodingException {
String authUrlBase = getAuthUrl().toString();
String queryAppendDelimiter = authUrlBase.contains("?") ? "&" : "?";
List<String> query = new ArrayList<>();
query.add("client_id=" + getRelyingPartyId());
query.add("response_type="+ URLEncoder.encode(getResponseType(), "UTF-8"));
query.add("redirect_uri=" + URLEncoder.encode(baseURL + "/login/callback/" + alias, "UTF-8"));
if (getScopes() != null && !getScopes().isEmpty()) {
query.add("scope=" + URLEncoder.encode(String.join(" ", getScopes()), "UTF-8"));
}
if (OIDCIdentityProviderDefinition.class.equals(getParameterizedClass())) {
final RandomValueStringGenerator nonceGenerator = new RandomValueStringGenerator(12);
query.add("nonce=" + nonceGenerator.generate());
}
String queryString = String.join("&", query);
return authUrlBase + queryAppendDelimiter + queryString;
}
}
Expand Up @@ -26,6 +26,7 @@
import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning;
import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.provider.oauth.XOAuthProviderConfigurator;
import org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken;
import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator;
import org.cloudfoundry.identity.uaa.provider.saml.SamlRedirectUtils;
Expand Down Expand Up @@ -88,7 +89,9 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static java.util.Collections.emptyMap;
import static java.util.Objects.isNull;
import static java.util.Optional.ofNullable;
import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OAUTH20;
import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OIDC10;
import static org.cloudfoundry.identity.uaa.constants.OriginKeys.UAA;
Expand Down Expand Up @@ -122,7 +125,7 @@ public class LoginInfoEndpoint {
public static final String ZONE_NAME = "zone_name";
public static final String ENTITY_ID = "entityID";
public static final String IDP_DEFINITIONS = "idpDefinitions";
public static final String OAUTH_DEFINITIONS = "oauthDefinitions";
public static final String OAUTH_LINKS = "oauthLinks";

private Properties gitProperties = new Properties();

Expand All @@ -146,7 +149,18 @@ public class LoginInfoEndpoint {
private ClientDetailsService clientDetailsService;

private IdentityProviderProvisioning providerProvisioning;
private static MapCollector<IdentityProvider, String, AbstractXOAuthIdentityProviderDefinition> idpsMapCollector = new MapCollector<>(idp -> idp.getOriginKey(), idp -> (AbstractXOAuthIdentityProviderDefinition) idp.getConfig());
private MapCollector<IdentityProvider, String, AbstractXOAuthIdentityProviderDefinition> idpsMapCollector =
new MapCollector<>(
idp -> idp.getOriginKey(),
idp -> (AbstractXOAuthIdentityProviderDefinition) idp.getConfig()
);

private XOAuthProviderConfigurator xoAuthProviderConfigurator;

public LoginInfoEndpoint setXoAuthProviderConfigurator(XOAuthProviderConfigurator xoAuthProviderConfigurator) {
this.xoAuthProviderConfigurator = xoAuthProviderConfigurator;
return this;
}

public void setExpiringCodeStore(ExpiringCodeStore expiringCodeStore) {
this.expiringCodeStore = expiringCodeStore;
Expand Down Expand Up @@ -231,7 +245,7 @@ public String loginForHtml(Model model, Principal principal, HttpServletRequest
}

private static <T extends SavedAccountOption> List<T> getSavedAccounts(Cookie[] cookies, Class<T> clazz) {
return Arrays.asList(Optional.ofNullable(cookies).orElse(new Cookie[]{}))
return Arrays.asList(ofNullable(cookies).orElse(new Cookie[]{}))
.stream()
.filter(c -> c.getName().startsWith("Saved-Account"))
.map(c -> JsonUtils.readValue(c.getValue(), clazz))
Expand Down Expand Up @@ -297,10 +311,10 @@ private String login(Model model, Principal principal, List<String> excludedProm

Map.Entry<String, AbstractIdentityProviderDefinition> idpForRedirect = null;

Optional<String> loginHintParam = Optional
.ofNullable(session)
.flatMap(s -> Optional.ofNullable((SavedRequest) s.getAttribute(SAVED_REQUEST_SESSION_ATTRIBUTE)))
.flatMap(sr -> Optional.ofNullable(sr.getParameterValues("login_hint")))
Optional<String> loginHintParam =
ofNullable(session)
.flatMap(s -> ofNullable((SavedRequest) s.getAttribute(SAVED_REQUEST_SESSION_ATTRIBUTE)))
.flatMap(sr -> ofNullable(sr.getParameterValues("login_hint")))
.flatMap(lhValues -> Arrays.asList(lhValues).stream().findFirst());

if(loginHintParam.isPresent()) {
Expand Down Expand Up @@ -351,7 +365,19 @@ private String login(Model model, Principal principal, List<String> excludedProm
model.addAttribute(LINK_CREATE_ACCOUNT_SHOW, linkCreateAccountShow);
model.addAttribute(FIELD_USERNAME_SHOW, fieldUsernameShow);
model.addAttribute(IDP_DEFINITIONS, samlIdps.values());
model.addAttribute(OAUTH_DEFINITIONS, oauthIdentityProviderDefinitions);
Map<String, String> oauthLinks = new HashMap<>();
ofNullable(oauthIdentityProviderDefinitions).orElse(emptyMap()).entrySet().stream()
.filter(e -> e.getValue().isShowLinkText() == true)
.forEach(e ->
oauthLinks.put(
xoAuthProviderConfigurator.getCompleteAuthorizationURI(
e.getKey(),
UaaUrlUtils.getBaseURL(request),
e.getValue()),
e.getValue().getLinkText()
)
);
model.addAttribute(OAUTH_LINKS, oauthLinks);
model.addAttribute("clientName", clientName);
}
model.addAttribute(LINKS, links);
Expand Down Expand Up @@ -438,7 +464,7 @@ private String redirectToExternalProvider(AbstractIdentityProviderDefinition idp
}

private String getRedirectUrlForXOAuthIDP(HttpServletRequest request, String alias, AbstractXOAuthIdentityProviderDefinition definition) throws UnsupportedEncodingException {
return definition.getCompleteAuthorizationURI(UaaUrlUtils.getBaseURL(request), alias);
return xoAuthProviderConfigurator.getCompleteAuthorizationURI(alias, UaaUrlUtils.getBaseURL(request), definition);
}

protected Map<String, SamlIdentityProviderDefinition> getSamlIdentityProviderDefinitions(List<String> allowedIdps) {
Expand Down
@@ -0,0 +1,51 @@
/*
* ******************************************************************************
* 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 org.cloudfoundry.identity.uaa.provider.AbstractXOAuthIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;

public class XOAuthProviderConfigurator {

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);
}
}

}
4 changes: 2 additions & 2 deletions server/src/main/resources/templates/web/login.html
Expand Up @@ -32,9 +32,9 @@ <h1 th:text="${T(org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder).uaa ? 'W
<div th:each="idp : ${idpDefinitions}" th:if="${idp.showSamlLink}">
<a href="" th:href="@{saml/discovery(returnIDParam=idp,entityID=${entityID},idp=${idp.idpEntityAlias},isPassive=true)}" th:text="${idp.linkText}" class="saml-login-link">Use your corporate credentials</a>
</div>
<div th:each="oauthIdp : ${oauthDefinitions}" th:if="${oauthIdp.value.showLinkText}">
<div th:each="oauthLink : ${oauthLinks}" >
<div>
<a href="" th:href="@{${oauthIdp.value.getCompleteAuthorizationURI(T(org.cloudfoundry.identity.uaa.util.UaaUrlUtils).getBaseURL(#httpServletRequest), oauthIdp.key)}}" th:text="${oauthIdp.value.linkText}" class="saml-login-link">Use your corporate credentials</a>
<a href="" th:href="${oauthLink.key}" th:text="${oauthLink.value}" class="saml-login-link">Use your corporate credentials</a>
</div>
</div>
</div>
Expand Down
Expand Up @@ -26,6 +26,7 @@
import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.provider.OIDCIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.provider.oauth.XOAuthProviderConfigurator;
import org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken;
import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
Expand Down Expand Up @@ -63,6 +64,7 @@
import java.util.List;
import java.util.Map;

import static org.cloudfoundry.identity.uaa.login.LoginInfoEndpoint.OAUTH_LINKS;
import static org.cloudfoundry.identity.uaa.login.LoginInfoEndpoint.SHOW_LOGIN_LINKS;
import static org.cloudfoundry.identity.uaa.util.UaaUrlUtils.addSubdomainToUrl;
import static org.cloudfoundry.identity.uaa.web.UaaSavedRequestAwareAuthenticationSuccessHandler.SAVED_REQUEST_SESSION_ATTRIBUTE;
Expand Down Expand Up @@ -99,6 +101,7 @@ public class LoginInfoEndpointTests {
private IdentityProviderProvisioning identityProviderProvisioning;
private IdentityProvider uaaProvider;
private IdentityZoneConfiguration originalConfiguration;
private XOAuthProviderConfigurator configurator;

@Before
public void setUpPrincipal() {
Expand All @@ -116,6 +119,8 @@ public void setUpPrincipal() {
idps = getIdps();
originalConfiguration = IdentityZoneHolder.get().getConfig();
IdentityZoneHolder.get().setConfig(new IdentityZoneConfiguration());
configurator = new XOAuthProviderConfigurator();

}

@After
Expand Down Expand Up @@ -611,7 +616,7 @@ public void allowedIdpsforClientOIDCProvider() throws MalformedURLException {
endpoint.setClientDetailsService(clientDetailsService);
endpoint.loginForHtml(model, null, request);

Map<String, AbstractXOAuthIdentityProviderDefinition> idpDefinitions = (Map<String, AbstractXOAuthIdentityProviderDefinition>) model.asMap().get("oauthDefinitions");
Map<String, AbstractXOAuthIdentityProviderDefinition> idpDefinitions = (Map<String, AbstractXOAuthIdentityProviderDefinition>) model.asMap().get(OAUTH_LINKS);
assertEquals(2, idpDefinitions.size());
}

Expand Down Expand Up @@ -710,6 +715,7 @@ private LoginInfoEndpoint getEndpoint() {
endpoint.setIdpDefinitions(emptyConfigurator);
IdentityZoneHolder.get().getConfig().setPrompts(prompts);
endpoint.setProviderProvisioning(identityProviderProvisioning);
endpoint.setXoAuthProviderConfigurator(configurator);
return endpoint;
}

Expand Down Expand Up @@ -739,7 +745,10 @@ private IdentityProvider createOIDCIdentityProvider(String originKey) throws Mal
IdentityProvider<AbstractXOAuthIdentityProviderDefinition> oidcIdentityProvider= new IdentityProvider<>();
oidcIdentityProvider.setOriginKey(originKey);
oidcIdentityProvider.setType(OriginKeys.OIDC10);
oidcIdentityProvider.setConfig(new OIDCIdentityProviderDefinition());
OIDCIdentityProviderDefinition definition = new OIDCIdentityProviderDefinition();
definition.setAuthUrl(new URL("https://"+originKey+".com"));
oidcIdentityProvider.setConfig(definition);

return oidcIdentityProvider;

}
Expand Down
Expand Up @@ -15,6 +15,7 @@

package org.cloudfoundry.identity.uaa.provider;

import org.cloudfoundry.identity.uaa.provider.oauth.XOAuthProviderConfigurator;
import org.cloudfoundry.identity.uaa.util.UaaUrlUtils;
import org.junit.Before;
import org.junit.Test;
Expand All @@ -32,7 +33,7 @@
import static org.springframework.http.HttpMethod.GET;


public class XOAuthIdentityProviderDefinitionTestx {
public class XOAuthProviderConfiguratorTests {

private OIDCIdentityProviderDefinition oidc;
private RawXOAuthIdentityProviderDefinition oauth;
Expand All @@ -41,6 +42,7 @@ public class XOAuthIdentityProviderDefinitionTestx {
private String baseExpect = "https://oidc10.identity.cf-app.com/oauth/authorize?client_id=%s&response_type=%s&redirect_uri=%s&scope=%s%s";
private String redirectUri;
private MockHttpServletRequest request;
XOAuthProviderConfigurator configurator;

@Before
public void setup() throws MalformedURLException {
Expand All @@ -67,6 +69,7 @@ public void setup() throws MalformedURLException {
}

redirectUri = URLEncoder.encode("https://localhost:8443/uaa/login/callback/alias");
configurator = new XOAuthProviderConfigurator();
}

@Test
Expand All @@ -78,12 +81,12 @@ public void getParameterizedClass() throws Exception {
@Test
public void nonce_included_on_oidc() throws UnsupportedEncodingException {
String expected = String.format(baseExpect, oidc.getRelyingPartyId(), URLEncoder.encode("id_token code"), redirectUri, URLEncoder.encode("openid password.write"), "&nonce=");
assertThat(oidc.getCompleteAuthorizationURI(UaaUrlUtils.getBaseURL(request), "alias"), startsWith(expected));
assertThat(configurator.getCompleteAuthorizationURI("alias", UaaUrlUtils.getBaseURL(request), oidc), startsWith(expected));
}

@Test
public void nonce_not_included_on_oauth() throws UnsupportedEncodingException {
String expected = String.format(baseExpect, oauth.getRelyingPartyId(), URLEncoder.encode("code"), redirectUri, URLEncoder.encode("openid password.write"), "");
assertEquals(oauth.getCompleteAuthorizationURI(UaaUrlUtils.getBaseURL(request), "alias"), expected);
assertEquals(configurator.getCompleteAuthorizationURI("alias", UaaUrlUtils.getBaseURL(request), oauth), expected);
}
}
}
3 changes: 3 additions & 0 deletions uaa/src/main/webapp/WEB-INF/spring-servlet.xml
Expand Up @@ -391,6 +391,8 @@
</bean>
</util:list>

<bean id="xoauthProviderConfigurator" class="org.cloudfoundry.identity.uaa.provider.oauth.XOAuthProviderConfigurator"/>

<!--Basic application beans. -->
<bean id="loginInfoEndpoint" class="org.cloudfoundry.identity.uaa.login.LoginInfoEndpoint">
<property name="authenticationManager" ref="zoneAwareAuthzAuthenticationManager"/>
Expand All @@ -401,6 +403,7 @@
<property name="expiringCodeStore" ref="codeStore"/>
<property name="externalLoginUrl" value="${login.url:''}"/>
<property name="providerProvisioning" ref="identityProviderProvisioning"/>
<property name="xoAuthProviderConfigurator" ref="xoauthProviderConfigurator"/>
</bean>

<bean id="healthzEndpoint" class="org.cloudfoundry.identity.uaa.health.HealthzEndpoint" />
Expand Down

0 comments on commit c2ad4cf

Please sign in to comment.