Skip to content

Commit

Permalink
Include nonce parameter if OIDC 1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
fhanik committed Nov 4, 2016
1 parent 492df57 commit e670c6e
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 26 deletions.
Expand Up @@ -13,9 +13,15 @@

package org.cloudfoundry.identity.uaa.provider;

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 @@ -140,4 +146,30 @@ public T setResponseType(String responseType) {
this.responseType = responseType;
return (T) this;
}

@JsonIgnore
public Class getParameterizedClass() {
ParameterizedType parameterizedType =
(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 @@ -34,6 +34,7 @@
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.util.MapCollector;
import org.cloudfoundry.identity.uaa.util.UaaStringUtils;
import org.cloudfoundry.identity.uaa.util.UaaUrlUtils;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
Expand All @@ -50,7 +51,6 @@
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -69,13 +69,11 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
Expand Down Expand Up @@ -440,19 +438,7 @@ private String redirectToExternalProvider(AbstractIdentityProviderDefinition idp
}

private String getRedirectUrlForXOAuthIDP(HttpServletRequest request, String alias, AbstractXOAuthIdentityProviderDefinition definition) throws UnsupportedEncodingException {
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"));
String requestURL = request.getRequestURL().toString();
String rootContext = StringUtils.hasText(request.getServletPath()) ? requestURL.substring(0, requestURL.indexOf(request.getServletPath())) : requestURL;
query.add("redirect_uri=" + URLEncoder.encode(rootContext + "/login/callback/" + alias, "UTF-8"));
if (definition.getScopes() != null && !definition.getScopes().isEmpty()) query.add("scope=" + URLEncoder.encode(String.join(" ", definition.getScopes()), "UTF-8"));
String queryString = String.join("&", query);

return authUrlBase + queryAppendDelimiter + queryString;
return definition.getCompleteAuthorizationURI(UaaUrlUtils.getBaseURL(request), alias);
}

protected Map<String, SamlIdentityProviderDefinition> getSamlIdentityProviderDefinitions(List<String> allowedIdps) {
Expand Down
Expand Up @@ -89,6 +89,15 @@ public static String getHostForURI(String uri) {
return b.build().getHost();
}

public static String getBaseURL(HttpServletRequest request) {
//returns scheme, host and context path
//for example http://localhost:8080/uaa or http://login.identity.cf-app.com
String requestURL = request.getRequestURL().toString();
return StringUtils.hasText(request.getServletPath()) ?
requestURL.substring(0, requestURL.indexOf(request.getServletPath())) :
requestURL;
}

public static Map<String, String[]> getParameterMap(String uri) {
UriComponentsBuilder b = UriComponentsBuilder.fromUriString(uri);
MultiValueMap<String, String> map = b.build().getQueryParams();
Expand Down
7 changes: 2 additions & 5 deletions server/src/main/resources/templates/web/login.html
Expand Up @@ -33,11 +33,8 @@ <h1 th:text="${T(org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder).uaa ? 'W
<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:if="${oauthIdp.value.scopes == null}">
<a href="" th:href="@{${oauthIdp.value.authUrl}(client_id=${oauthIdp.value.relyingPartyId},response_type=${oauthIdp.value.responseType},redirect_uri=${#httpServletRequest.requestURL + '/callback/' + oauthIdp.key})}" th:text="${oauthIdp.value.linkText}" class="saml-login-link">Use your corporate credentials</a>
</div>
<div th:if="${oauthIdp.value.scopes != null}">
<a href="" th:href="@{${oauthIdp.value.authUrl}(client_id=${oauthIdp.value.relyingPartyId},response_type=code,redirect_uri=${#httpServletRequest.requestURL + '/callback/' + oauthIdp.key},scope=${#strings.listJoin(oauthIdp.value.scopes,' ')})}" th:text="${oauthIdp.value.linkText}" class="saml-login-link">Use your corporate credentials</a>
<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>
</div>
</div>
</div>
Expand Down
@@ -0,0 +1,89 @@
/*
* ****************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2016] 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;

import org.cloudfoundry.identity.uaa.util.UaaUrlUtils;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;

import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Arrays;

import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.springframework.http.HttpMethod.GET;


public class XOAuthIdentityProviderDefinitionTestx {

private OIDCIdentityProviderDefinition oidc;
private RawXOAuthIdentityProviderDefinition oauth;


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;

@Before
public void setup() throws MalformedURLException {
oidc = new OIDCIdentityProviderDefinition();
oauth = new RawXOAuthIdentityProviderDefinition();
request = new MockHttpServletRequest(GET.name(), "/uaa/login");
request.setContextPath("/uaa");
request.setServletPath("/login");
request.setScheme("https");
request.setServerName("localhost");
request.setServerPort(8443);

for (AbstractXOAuthIdentityProviderDefinition def : Arrays.asList(oidc, oauth)) {
def.setAuthUrl(new URL("https://oidc10.identity.cf-app.com/oauth/authorize"));
def.setTokenUrl(new URL("https://oidc10.identity.cf-app.com/oauth/token"));
def.setTokenKeyUrl(new URL("https://oidc10.identity.cf-app.com/token_keys"));
def.setScopes(Arrays.asList("openid","password.write"));
def.setRelyingPartyId("clientId");
if (def == oidc) {
def.setResponseType("id_token code");
} else {
def.setResponseType("code");
}
}

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

@Test
public void getParameterizedClass() throws Exception {
assertEquals(OIDCIdentityProviderDefinition.class, oidc.getParameterizedClass());
assertEquals(RawXOAuthIdentityProviderDefinition.class, oauth.getParameterizedClass());
}

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

@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);
}
}
Expand Up @@ -105,6 +105,7 @@
import static org.hamcrest.Matchers.isEmptyOrNullString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
Expand All @@ -123,6 +124,7 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
Expand Down Expand Up @@ -1175,7 +1177,12 @@ public void xOAuthRedirect_onlyOneProvider_noClientContext_and_ResponseType_Set(
.servletPath("/login")
.with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost")))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://auth.url?client_id=uaa&response_type=code+id_token&redirect_uri=http%3A%2F%2F" + identityZone.getSubdomain() + ".localhost%2Flogin%2Fcallback%2F" + oauthAlias + "&scope=openid+roles"));
.andExpect(
header()
.string("Location",
startsWith("http://auth.url?client_id=uaa&response_type=code+id_token&redirect_uri=http%3A%2F%2F" + identityZone.getSubdomain() + ".localhost%2Flogin%2Fcallback%2F" + oauthAlias + "&scope=openid+roles&nonce=")
)
);
IdentityZoneHolder.clear();
}

Expand Down Expand Up @@ -1223,7 +1230,12 @@ public void testLoginHintRedirect() throws Exception {
.with(new SetServerNameRequestPostProcessor(identityZone.getSubdomain() + ".localhost"))
)
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://auth.url?client_id=uaa&response_type=code&redirect_uri=http%3A%2F%2F" + identityZone.getSubdomain() + ".localhost%2Flogin%2Fcallback%2F" + oauthAlias + "&scope=openid+roles"));
.andExpect(
header()
.string("Location",
startsWith("http://auth.url?client_id=uaa&response_type=code&redirect_uri=http%3A%2F%2F" + identityZone.getSubdomain() + ".localhost%2Flogin%2Fcallback%2F" + oauthAlias + "&scope=openid+roles&nonce=")
)
);
IdentityZoneHolder.clear();


Expand Down Expand Up @@ -1976,7 +1988,13 @@ public void idpDiscoveryRedirectsToOIDCProvider() throws Exception {
.servletPath("/login/idp_discovery")
.param("email", "marissa@test.org")
.with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost")))
.andExpect(redirectedUrl("http://myauthurl.com?client_id=id&response_type=id_token+code&redirect_uri=http%3A%2F%2Foidc-idp-discovery.localhost%2Flogin%2Fcallback%2F" +originKey));
.andExpect(
header()
.string(
"Location",
startsWith("http://myauthurl.com?client_id=id&response_type=id_token+code&redirect_uri=http%3A%2F%2Foidc-idp-discovery.localhost%2Flogin%2Fcallback%2F" +originKey+"&nonce=")
)
);
}

@Test
Expand All @@ -1989,10 +2007,11 @@ public void multiple_oidc_providers_use_response_type_in_url() throws Exception

getMockMvc().perform(get("/login")
.header("Accept", TEXT_HTML)
.servletPath("/login")
.with(new SetServerNameRequestPostProcessor(zone.getSubdomain() + ".localhost")))
.andExpect(status().isOk())
.andExpect(content().string(containsString("http://myauthurl.com?client_id=id&amp;response_type=code&amp;redirect_uri=http%3A%2F%2Foidc-idp-discovery-multi.localhost%2Flogin%2Fcallback%2F" +originKey)))
.andExpect(content().string(containsString("http://myauthurl.com?client_id=id&amp;response_type=code+id_token&amp;redirect_uri=http%3A%2F%2Foidc-idp-discovery-multi.localhost%2Flogin%2Fcallback%2F" +originKey2)));
.andExpect(content().string(containsString("http://myauthurl.com?client_id=id&amp;response_type=code&amp;redirect_uri=http%3A%2F%2Foidc-idp-discovery-multi.localhost%2Flogin%2Fcallback%2F" +originKey+"&amp;nonce=")))
.andExpect(content().string(containsString("http://myauthurl.com?client_id=id&amp;response_type=code+id_token&amp;redirect_uri=http%3A%2F%2Foidc-idp-discovery-multi.localhost%2Flogin%2Fcallback%2F" +originKey2+"&amp;nonce=")));

}

Expand Down

0 comments on commit e670c6e

Please sign in to comment.