diff --git a/docs/UAA-APIs.rst b/docs/UAA-APIs.rst index 0f87ad4383f..ee26bca1e5f 100644 --- a/docs/UAA-APIs.rst +++ b/docs/UAA-APIs.rst @@ -1359,7 +1359,7 @@ Fields *Available Fields* :: externalGroupsWhitelist List Optional List of external groups that will be included in the ID Token if the `roles` scope is requested. providerDescription String Optional Human readable name/description of this provider - OAuth Provider Configuration (provided in JSON format as part of the ``config`` field on the Identity Provider - See class org.cloudfoundry.identity.uaa.provider.OauthIdentityProviderDefinition + OAuth Provider Configuration (provided in JSON format as part of the ``config`` field on the Identity Provider - See class org.cloudfoundry.identity.uaa.provider.XOAuthIdentityProviderDefinition ====================== ====================== ======== ================================================================================================================================================================================================================================================================================================================================================================================================================================================= alias String Required Must match ``originKey`` in the provider definition authUrl URL Required Must be a valid URL that returns the authorization code. diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java index eb8b9f12817..8b88258959a 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/IdentityProvider.java @@ -17,6 +17,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; @@ -134,12 +135,8 @@ public IdentityProvider setConfig(T config) { } } else if (UaaIdentityProviderDefinition.class.isAssignableFrom(clazz)) { this.type = UAA; - } else if (OauthIdentityProviderDefinition.class.isAssignableFrom(clazz)) { - if (((OauthIdentityProviderDefinition)config).getUserInfoUrl()==null) { - this.type = OAUTH20; - } else { - this.type = OIDC10; - } + } else if (XOAuthIdentityProviderDefinition.class.isAssignableFrom(clazz)) { + this.type = ((XOAuthIdentityProviderDefinition) config).getAuthenticationFlow().getType(); } else if (LdapIdentityProviderDefinition.class.isAssignableFrom(clazz)) { this.type = LDAP; @@ -340,7 +337,10 @@ public IdentityProvider deserialize(JsonParser jp, DeserializationContext ctxt) definition = JsonUtils.readValue(config, SamlIdentityProviderDefinition.class); break; case OAUTH20: - definition = JsonUtils.readValue(config, OauthIdentityProviderDefinition.class); + definition = JsonUtils.readValue(config, new TypeReference>() {}); + break; + case OIDC10: + definition = JsonUtils.readValue(config, new TypeReference>() {}); break; case UAA: definition = JsonUtils.readValue(config, UaaIdentityProviderDefinition.class); diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/OidcAuthenticationFlow.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/OidcAuthenticationFlow.java new file mode 100644 index 00000000000..3293526ec6c --- /dev/null +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/OidcAuthenticationFlow.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * 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 com.fasterxml.jackson.annotation.JsonIgnore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; + +import java.net.URL; +import java.util.Map; + +public class OidcAuthenticationFlow implements XOAuthIdentityProviderDefinition.AuthenticationFlow { + + private URL userInfoUrl; + + @Override + @JsonIgnore + public String getType() { + return OriginKeys.OIDC10; + } + + @Override + @JsonIgnore + public String getResponseType() { + return "id_token"; + } + + @Override + @JsonIgnore + public String getTokenFromResponse(Map responseBody) { + return responseBody.get("id_token"); + } + + public OidcAuthenticationFlow setUserInfoUrl(URL userInfoUrl) { + this.userInfoUrl = userInfoUrl; + return this; + } + + public URL getUserInfoUrl() { + return userInfoUrl; + } +} diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/RawOauthAuthenticationFlow.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/RawOauthAuthenticationFlow.java new file mode 100644 index 00000000000..1fe8f338664 --- /dev/null +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/RawOauthAuthenticationFlow.java @@ -0,0 +1,49 @@ +package org.cloudfoundry.identity.uaa.provider; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; + +import java.util.Map; + +/******************************************************************************* + * 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. + *******************************************************************************/ +public class RawOauthAuthenticationFlow implements XOAuthIdentityProviderDefinition.AuthenticationFlow { + + @Override + @JsonIgnore + public String getType() { + return OriginKeys.OAUTH20; + } + + @Override + @JsonIgnore + public String getResponseType() { + return "token"; + } + + @Override + @JsonIgnore + public String getTokenFromResponse(Map responseBody) { + return responseBody.get("access_token"); + } + + private String ohmygodwhatever; + + public String getOhmygodwhatever() { + return ohmygodwhatever; + } + + public void setOhmygodwhatever(String ohmygodwhatever) { + this.ohmygodwhatever = ohmygodwhatever; + } +} diff --git a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/OauthIdentityProviderDefinition.java b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/XOAuthIdentityProviderDefinition.java similarity index 60% rename from model/src/main/java/org/cloudfoundry/identity/uaa/provider/OauthIdentityProviderDefinition.java rename to model/src/main/java/org/cloudfoundry/identity/uaa/provider/XOAuthIdentityProviderDefinition.java index 96d63f32d40..933315ca16e 100644 --- a/model/src/main/java/org/cloudfoundry/identity/uaa/provider/OauthIdentityProviderDefinition.java +++ b/model/src/main/java/org/cloudfoundry/identity/uaa/provider/XOAuthIdentityProviderDefinition.java @@ -16,26 +16,35 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.net.URL; +import java.util.Map; @JsonIgnoreProperties(ignoreUnknown = true) -public class OauthIdentityProviderDefinition extends ExternalIdentityProviderDefinition { +public class XOAuthIdentityProviderDefinition extends ExternalIdentityProviderDefinition { private URL authUrl; private URL tokenUrl; private URL tokenKeyUrl; - private URL userInfoUrl; private String tokenKey; private String linkText; private boolean showLinkText = true; private boolean skipSslValidation; private String relyingPartyId; private String relyingPartySecret; + private TAuthenticationFlow authenticationFlow; + public TAuthenticationFlow getAuthenticationFlow() { + return authenticationFlow; + } + + public XOAuthIdentityProviderDefinition setAuthenticationFlow(TAuthenticationFlow authenticationFlow) { + this.authenticationFlow = authenticationFlow; + return this; + } public URL getAuthUrl() { return authUrl; } - public OauthIdentityProviderDefinition setAuthUrl(URL authUrl) { + public XOAuthIdentityProviderDefinition setAuthUrl(URL authUrl) { this.authUrl = authUrl; return this; } @@ -44,7 +53,7 @@ public URL getTokenUrl() { return tokenUrl; } - public OauthIdentityProviderDefinition setTokenUrl(URL tokenUrl) { + public XOAuthIdentityProviderDefinition setTokenUrl(URL tokenUrl) { this.tokenUrl = tokenUrl; return this; } @@ -53,7 +62,7 @@ public URL getTokenKeyUrl() { return tokenKeyUrl; } - public OauthIdentityProviderDefinition setTokenKeyUrl(URL tokenKeyUrl) { + public XOAuthIdentityProviderDefinition setTokenKeyUrl(URL tokenKeyUrl) { this.tokenKeyUrl = tokenKeyUrl; return this; } @@ -62,7 +71,7 @@ public String getTokenKey() { return tokenKey; } - public OauthIdentityProviderDefinition setTokenKey(String tokenKey) { + public XOAuthIdentityProviderDefinition setTokenKey(String tokenKey) { this.tokenKey = tokenKey; return this; } @@ -71,7 +80,7 @@ public String getLinkText() { return linkText; } - public OauthIdentityProviderDefinition setLinkText(String linkText) { + public XOAuthIdentityProviderDefinition setLinkText(String linkText) { this.linkText = linkText; return this; } @@ -80,7 +89,7 @@ public boolean isShowLinkText() { return showLinkText; } - public OauthIdentityProviderDefinition setShowLinkText(boolean showLinkText) { + public XOAuthIdentityProviderDefinition setShowLinkText(boolean showLinkText) { this.showLinkText = showLinkText; return this; } @@ -89,7 +98,7 @@ public String getRelyingPartyId() { return relyingPartyId; } - public OauthIdentityProviderDefinition setRelyingPartyId(String relyingPartyId) { + public XOAuthIdentityProviderDefinition setRelyingPartyId(String relyingPartyId) { this.relyingPartyId = relyingPartyId; return this; } @@ -98,7 +107,7 @@ public String getRelyingPartySecret() { return relyingPartySecret; } - public OauthIdentityProviderDefinition setRelyingPartySecret(String relyingPartySecret) { + public XOAuthIdentityProviderDefinition setRelyingPartySecret(String relyingPartySecret) { this.relyingPartySecret = relyingPartySecret; return this; } @@ -107,17 +116,17 @@ public boolean isSkipSslValidation() { return skipSslValidation; } - public OauthIdentityProviderDefinition setSkipSslValidation(boolean skipSslValidation) { + public XOAuthIdentityProviderDefinition setSkipSslValidation(boolean skipSslValidation) { this.skipSslValidation = skipSslValidation; return this; } - public URL getUserInfoUrl() { - return userInfoUrl; - } + public interface AuthenticationFlow { + String getType(); + + String getResponseType(); + + String getTokenFromResponse(Map responseBody); - public OauthIdentityProviderDefinition setUserInfoUrl(URL userInfoUrl) { - this.userInfoUrl = userInfoUrl; - return this; } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java index 9d1326f2902..4798bd65fcc 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/authentication/UaaAuthentication.java @@ -37,7 +37,7 @@ @JsonDeserialize(using = UaaAuthenticationDeserializer.class) public class UaaAuthentication implements Authentication, Serializable { - private List authorities; + private Collection authorities; private Object credentials; private UaaPrincipal principal; private UaaAuthenticationDetails details; @@ -58,14 +58,14 @@ public class UaaAuthentication implements Authentication, Serializable { * principal represented by this authentication object. */ public UaaAuthentication(UaaPrincipal principal, - List authorities, + Collection authorities, UaaAuthenticationDetails details) { this(principal, null, authorities, details, true, System.currentTimeMillis()); } public UaaAuthentication(UaaPrincipal principal, Object credentials, - List authorities, + Collection authorities, UaaAuthenticationDetails details, boolean authenticated, long authenticatedTime) { @@ -74,7 +74,7 @@ public UaaAuthentication(UaaPrincipal principal, public UaaAuthentication(UaaPrincipal principal, Object credentials, - List authorities, + Collection authorities, UaaAuthenticationDetails details, boolean authenticated, long authenticatedTime, diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java index 1619adec127..9aa0536ec99 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/impl/config/IdentityProviderBootstrap.java @@ -20,7 +20,7 @@ import org.cloudfoundry.identity.uaa.provider.KeystoneIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; -import org.cloudfoundry.identity.uaa.provider.OauthIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.XOAuthIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; @@ -50,7 +50,7 @@ public class IdentityProviderBootstrap implements InitializingBean { private IdentityProviderProvisioning provisioning; private List providers = new LinkedList<>(); private SamlIdentityProviderConfigurator configurator; - private Map oauthIdpDefintions; + private Map oauthIdpDefintions; private Map ldapConfig; private Map keystoneConfig; private Environment environment; @@ -71,14 +71,10 @@ private void addOauthProviders() { if (oauthIdpDefintions == null) { return; } - for (Map.Entry definition : oauthIdpDefintions.entrySet()) { + for (Map.Entry definition : oauthIdpDefintions.entrySet()) { validateDuplicateAlias(definition.getKey()); IdentityProvider provider = new IdentityProvider(); - if (definition.getValue().getUserInfoUrl()==null) { - provider.setType(OriginKeys.OAUTH20); - } else { - provider.setType(OriginKeys.OIDC10); - } + provider.setType(definition.getValue().getAuthenticationFlow().getType()); provider.setOriginKey(definition.getKey()); provider.setName("UAA Oauth Identity Provider["+provider.getOriginKey()+"]"); provider.setActive(true); @@ -282,7 +278,7 @@ public void setDisableInternalUserManagement(boolean disableInternalUserManageme this.disableInternalUserManagement = disableInternalUserManagement; } - public void setOauthIdpDefintions(Map oauthIdpDefintions) { + public void setOauthIdpDefintions(Map oauthIdpDefintions) { this.oauthIdpDefintions = oauthIdpDefintions; } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java index 0e64d07d41e..df7354828f6 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpoint.java @@ -25,13 +25,19 @@ import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator; import org.cloudfoundry.identity.uaa.provider.saml.SamlRedirectUtils; 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.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; @@ -42,12 +48,17 @@ import org.springframework.security.web.savedrequest.SavedRequest; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.client.RestTemplate; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; @@ -72,13 +83,13 @@ import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import static java.util.Objects.isNull; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OAUTH20; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OIDC10; import static org.cloudfoundry.identity.uaa.util.UaaUrlUtils.addSubdomainToUrl; import static org.springframework.util.StringUtils.hasText; +import static org.springframework.web.bind.annotation.RequestMethod.GET; /** * Controller that sends login info (e.g. prompts) to clients wishing to @@ -129,6 +140,7 @@ public class LoginInfoEndpoint { private ClientDetailsService clientDetailsService; private IdentityProviderProvisioning providerProvisioning; + private static MapCollector idpsMapCollector = new MapCollector<>(idp -> idp.getOriginKey(), idp -> (XOAuthIdentityProviderDefinition) idp.getConfig()); public void setExpiringCodeStore(ExpiringCodeStore expiringCodeStore) { this.expiringCodeStore = expiringCodeStore; @@ -211,7 +223,7 @@ private String login(Model model, Principal principal, List excludedProm List allowedIdps = getAllowedIdps(session); List idps = getSamlIdentityProviderDefinitions(allowedIdps); - List oauthIdentityProviderDefinitions = getOauthIdentityProviderDefinitions(); + Map oauthIdentityProviderDefinitions = getOauthIdentityProviderDefinitions(); boolean fieldUsernameShow = true; @@ -286,7 +298,7 @@ private String login(Model model, Principal principal, List excludedProm } } - for (OauthIdentityProviderDefinition oauthIdp : oauthIdentityProviderDefinitions) { + for (XOAuthIdentityProviderDefinition oauthIdp : oauthIdentityProviderDefinitions.values()) { if (oauthIdp.isShowLinkText()) { model.addAttribute(SHOW_LOGIN_LINKS, true); noIdpsPresent = false; @@ -313,12 +325,12 @@ protected List getSamlIdentityProviderDefinition } - protected List getOauthIdentityProviderDefinitions() { + protected Map getOauthIdentityProviderDefinitions() { final List types = Arrays.asList(OAUTH20, OIDC10); List identityProviders = providerProvisioning.retrieveAll(true, IdentityZoneHolder.get().getId()); - List identityProviderDefinitions = identityProviders.stream() + Map identityProviderDefinitions = identityProviders.stream() .filter(p -> types.contains(p.getType())) - .map(idp -> (OauthIdentityProviderDefinition) idp.getConfig()).collect(Collectors.toList()); + .collect(idpsMapCollector); return identityProviderDefinitions; } @@ -445,7 +457,7 @@ public AutologinResponse generateAutologinCode(@RequestBody AutologinRequest req return new AutologinResponse(expiringCode.getCode()); } - @RequestMapping(value = "/autologin", method = RequestMethod.GET) + @RequestMapping(value = "/autologin", method = GET) public String performAutologin(HttpSession session) { String redirectLocation = "home"; SavedRequest savedRequest = (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST"); @@ -456,7 +468,7 @@ public String performAutologin(HttpSession session) { return "redirect:" + redirectLocation; } - @RequestMapping(value = { "/passcode" }, method = RequestMethod.GET) + @RequestMapping(value = { "/passcode" }, method = GET) public String generatePasscode(Map model, Principal principal) throws NoSuchAlgorithmException, IOException { String username, origin, userId = NotANumber; @@ -501,6 +513,12 @@ public String generatePasscode(Map model, Principal principal) return PASSCODE; } + @RequestMapping(value = "/login/callback/{origin}", method = GET) + public String exchangeExternalCodeForToken() throws URISyntaxException { + + return "home"; + } + protected Map getLinksInfo() { IdentityZone zone = IdentityZoneHolder.get(); IdentityProvider uaaIdp = providerProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZoneHolder.get().getId()); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/JdbcIdentityProviderProvisioning.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/JdbcIdentityProviderProvisioning.java index dc9db67ca0b..7c5f4c2eecd 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/JdbcIdentityProviderProvisioning.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/JdbcIdentityProviderProvisioning.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.cloudfoundry.identity.uaa.provider; +import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.audit.event.SystemDeletable; @@ -193,8 +194,10 @@ public IdentityProvider mapRow(ResultSet rs, int rowNum) throws SQLException { definition = JsonUtils.readValue(config, SamlIdentityProviderDefinition.class); break; case OriginKeys.OAUTH20: + definition = JsonUtils.readValue(config, new TypeReference>() {}); + break; case OriginKeys.OIDC10 : - definition = JsonUtils.readValue(config, OauthIdentityProviderDefinition.class); + definition = JsonUtils.readValue(config, new TypeReference>() {}); break; case OriginKeys.UAA : definition = JsonUtils.readValue(config, UaaIdentityProviderDefinition.class); diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/OauthIdentityProviderDefinitionFactoryBean.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/OauthIdentityProviderDefinitionFactoryBean.java index fe65b501c1a..0b313ddd359 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/OauthIdentityProviderDefinitionFactoryBean.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/OauthIdentityProviderDefinitionFactoryBean.java @@ -1,47 +1,67 @@ package org.cloudfoundry.identity.uaa.provider.oauth; -import org.cloudfoundry.identity.uaa.provider.OauthIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.RawOauthAuthenticationFlow; +import org.cloudfoundry.identity.uaa.provider.XOAuthIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.OidcAuthenticationFlow; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.Map; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OAUTH20; +import static org.cloudfoundry.identity.uaa.constants.OriginKeys.OIDC10; import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.ATTRIBUTE_MAPPINGS; public class OauthIdentityProviderDefinitionFactoryBean { - private Map oauthIdpDefinitions = new HashMap<>(); + private Map oauthIdpDefinitions = new HashMap<>(); public OauthIdentityProviderDefinitionFactoryBean(Map definitions) { if (definitions != null) { for (String alias : definitions.keySet()) { - Map oauthIdpDefinitionMap = definitions.get(alias); - OauthIdentityProviderDefinition oauthIdpDefinition = new OauthIdentityProviderDefinition(); - oauthIdpDefinition.setLinkText((String)oauthIdpDefinitionMap.get("linkText")); - oauthIdpDefinition.setRelyingPartyId((String)oauthIdpDefinitionMap.get("relyingPartyId")); - oauthIdpDefinition.setRelyingPartySecret((String)oauthIdpDefinitionMap.get("relyingPartySecret")); - oauthIdpDefinition.setShowLinkText(oauthIdpDefinitionMap.get("showLinkText") == null ? true : (boolean) oauthIdpDefinitionMap.get("showLinkText")); - oauthIdpDefinition.setSkipSslValidation(oauthIdpDefinitionMap.get("skipSslValidation") == null ? false : (boolean) oauthIdpDefinitionMap.get("skipSslValidation")); - oauthIdpDefinition.setTokenKey((String)oauthIdpDefinitionMap.get("tokenKey")); - oauthIdpDefinition.setAttributeMappings((Map) oauthIdpDefinitionMap.get(ATTRIBUTE_MAPPINGS)); - try { - oauthIdpDefinition.setAuthUrl(new URL((String)oauthIdpDefinitionMap.get("authUrl"))); - oauthIdpDefinition.setTokenKeyUrl(oauthIdpDefinitionMap.get("tokenKeyUrl") == null ? null : new URL((String)oauthIdpDefinitionMap.get("tokenKeyUrl"))); - oauthIdpDefinition.setUserInfoUrl(oauthIdpDefinitionMap.get("userInfoUrl") == null ? null : new URL((String)oauthIdpDefinitionMap.get("userInfoUrl"))); - oauthIdpDefinition.setTokenUrl(new URL((String)oauthIdpDefinitionMap.get("tokenUrl"))); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("URL is malformed.", e); + Map idpDefinitionMap = definitions.get(alias); + String type = (String) idpDefinitionMap.get("type"); + if(OAUTH20.equalsIgnoreCase(type)) { + RawOauthAuthenticationFlow oauthIdentificationStrategy = new RawOauthAuthenticationFlow(); + XOAuthIdentityProviderDefinition oauthIdentityProviderDefinition = getOauthIdentityProviderDefinition(idpDefinitionMap, oauthIdentificationStrategy); + oauthIdpDefinitions.put(alias, oauthIdentityProviderDefinition); + } + else if(OIDC10.equalsIgnoreCase(type)) { + OidcAuthenticationFlow oidcIdentificationStrategy = new OidcAuthenticationFlow(); + XOAuthIdentityProviderDefinition oidcIdentityProviderDefinition = getOauthIdentityProviderDefinition(idpDefinitionMap, oidcIdentificationStrategy); + oauthIdpDefinitions.put(alias, oidcIdentityProviderDefinition); + } else { + throw new IllegalArgumentException("Unknown type for provider. Type must be oauth2.0 or oidc1.0. (Was " + type + ")"); } - oauthIdpDefinitions.put(alias, oauthIdpDefinition); } } } - public Map getOauthIdpDefinitions() { + private XOAuthIdentityProviderDefinition getOauthIdentityProviderDefinition(Map idpDefinitionMap, T idStrategy) { + XOAuthIdentityProviderDefinition idpDefinition = new XOAuthIdentityProviderDefinition<>(); + idpDefinition.setAuthenticationFlow(idStrategy); + idpDefinition.setLinkText((String)idpDefinitionMap.get("linkText")); + idpDefinition.setRelyingPartyId((String)idpDefinitionMap.get("relyingPartyId")); + idpDefinition.setRelyingPartySecret((String)idpDefinitionMap.get("relyingPartySecret")); + idpDefinition.setShowLinkText(idpDefinitionMap.get("showLinkText") == null ? true : (boolean) idpDefinitionMap.get("showLinkText")); + idpDefinition.setSkipSslValidation(idpDefinitionMap.get("skipSslValidation") == null ? false : (boolean) idpDefinitionMap.get("skipSslValidation")); + idpDefinition.setTokenKey((String)idpDefinitionMap.get("tokenKey")); + idpDefinition.setAttributeMappings((Map) idpDefinitionMap.get(ATTRIBUTE_MAPPINGS)); + try { + idpDefinition.setAuthUrl(new URL((String)idpDefinitionMap.get("authUrl"))); + idpDefinition.setTokenKeyUrl(idpDefinitionMap.get("tokenKeyUrl") == null ? null : new URL((String)idpDefinitionMap.get("tokenKeyUrl"))); + idpDefinition.setTokenUrl(new URL((String)idpDefinitionMap.get("tokenUrl"))); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("URL is malformed.", e); + } + return idpDefinition; + } + + public Map getOauthIdpDefinitions() { return oauthIdpDefinitions; } - public void setOauthIdpDefinitions(Map oauthIdpDefinitions) { + public void setOauthIdpDefinitions(Map oauthIdpDefinitions) { this.oauthIdpDefinitions = oauthIdpDefinitions; } } diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/XOAuthAuthenticationFilter.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/XOAuthAuthenticationFilter.java new file mode 100644 index 00000000000..b16246e81dc --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/XOAuthAuthenticationFilter.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * 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.oauth; + +import org.apache.commons.httpclient.util.URIUtil; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +public class XOAuthAuthenticationFilter implements Filter { + + + private final XOAuthAuthenticationManager xOAuthAuthenticationManager; + + public XOAuthAuthenticationFilter(XOAuthAuthenticationManager xOAuthAuthenticationManager) { + this.xOAuthAuthenticationManager = xOAuthAuthenticationManager; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + + String origin = URIUtil.getName(request.getServletPath()); + String code = request.getParameter("code"); + String redirectUrl = request.getRequestURL().toString(); + XOAuthCodeToken codeToken = new XOAuthCodeToken(code, origin, redirectUrl); + Authentication authentication = xOAuthAuthenticationManager.authenticate(codeToken); + SecurityContextHolder.getContext().setAuthentication(authentication); + chain.doFilter(request, response); + } + + @Override + public void destroy() { + + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/XOAuthAuthenticationManager.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/XOAuthAuthenticationManager.java new file mode 100644 index 00000000000..a592ee00927 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/XOAuthAuthenticationManager.java @@ -0,0 +1,110 @@ +package org.cloudfoundry.identity.uaa.provider.oauth; + +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.oauth.jwt.Jwt; +import org.cloudfoundry.identity.uaa.oauth.jwt.JwtHelper; +import org.cloudfoundry.identity.uaa.oauth.token.Claims; +import org.cloudfoundry.identity.uaa.oauth.token.CompositeAccessToken; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.OidcAuthenticationFlow; +import org.cloudfoundry.identity.uaa.provider.XOAuthIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import static org.cloudfoundry.identity.uaa.oauth.token.CompositeAccessToken.ID_TOKEN; + +/******************************************************************************* + * 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. + *******************************************************************************/ +public class XOAuthAuthenticationManager implements AuthenticationManager { + + private RestTemplate restTemplate = new RestTemplate(); + private IdentityProviderProvisioning providerProvisioning; + + public XOAuthAuthenticationManager(IdentityProviderProvisioning providerProvisioning) { + this.providerProvisioning = providerProvisioning; + } + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + XOAuthCodeToken codeToken = (XOAuthCodeToken) authentication; + + String origin = codeToken.getOrigin(); + String code = codeToken.getCode(); + IdentityProvider provider = providerProvisioning.retrieveByOrigin(origin, IdentityZoneHolder.get().getId()); + if (provider != null && provider.getConfig() instanceof XOAuthIdentityProviderDefinition) { + XOAuthIdentityProviderDefinition config = (XOAuthIdentityProviderDefinition) provider.getConfig(); + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("client_id", config.getRelyingPartyId()); + body.add("client_secret", config.getRelyingPartySecret()); + body.add("grant_type", "authorization_code"); + body.add("response_type", config.getAuthenticationFlow().getResponseType()); + body.add("code", code); + body.add("redirect_uri", codeToken.getRedirectUrl()); + + HttpHeaders headers = new HttpHeaders(); + headers.put("Content-Type", Arrays.asList("application/x-www-form-urlencoded")); + headers.put("Accept", Arrays.asList("application/json")); + + URI requestUri; + HttpEntity requestEntity = new HttpEntity<>(body, headers); + try { + requestUri = config.getTokenUrl().toURI(); + } catch (URISyntaxException e) { + return null; + } + + ResponseEntity> responseEntity = restTemplate.exchange(requestUri, HttpMethod.POST, requestEntity, new ParameterizedTypeReference>() {}); + String id_token = responseEntity.getBody().get(ID_TOKEN); + + OidcAuthenticationFlow authenticationFlow = (OidcAuthenticationFlow) config.getAuthenticationFlow(); + + HttpHeaders userInfoHeaders = new HttpHeaders(); + userInfoHeaders.add("Authorization", "bearer " + id_token); + + ResponseEntity> userInfoResponseEntity = restTemplate.exchange(authenticationFlow.getUserInfoUrl().toString(), HttpMethod.GET, new HttpEntity<>(null ,userInfoHeaders), new ParameterizedTypeReference>() {}); + + Claims claims = JsonUtils.readValue(JsonUtils.writeValueAsString(userInfoResponseEntity.getBody()), Claims.class); + + UaaUser user = new UaaUser(claims.getUserName(), null, claims.getEmail(), claims.getGivenName(), claims.getFamilyName(), origin, IdentityZoneHolder.get().getId()); + UaaPrincipal principal = new UaaPrincipal(user); + UaaAuthentication uaaAuthentication = new UaaAuthentication(principal, codeToken.getAuthorities(), null); + return uaaAuthentication; + } + + return null; + } + + public RestTemplate getRestTemplate() { + return restTemplate; + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/XOAuthCodeToken.java b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/XOAuthCodeToken.java new file mode 100644 index 00000000000..d9e7c71a594 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/XOAuthCodeToken.java @@ -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.oauth; + +import org.cloudfoundry.identity.uaa.user.UaaAuthority; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +public class XOAuthCodeToken implements Authentication { + private String code; + private String origin; + private String redirectUrl; + + public XOAuthCodeToken(String code, String origin, String redirectUrl) { + this.code = code; + this.origin = origin; + this.redirectUrl = redirectUrl; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public String getRedirectUrl() { + return redirectUrl; + } + + public void setRedirectUrl(String redirectUrl) { + this.redirectUrl = redirectUrl; + } + + @Override + public Collection getAuthorities() { + return UaaAuthority.USER_AUTHORITIES; + } + + @Override + public Object getCredentials() { + return getCode(); + } + + @Override + public Object getDetails() { + return null; + } + + @Override + public Object getPrincipal() { + return getCode(); + } + + @Override + public boolean isAuthenticated() { + return false; + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + } + + @Override + public String getName() { + return getCode(); + } +} diff --git a/server/src/main/resources/templates/web/login.html b/server/src/main/resources/templates/web/login.html index a6b4042634e..e51ea302eaf 100644 --- a/server/src/main/resources/templates/web/login.html +++ b/server/src/main/resources/templates/web/login.html @@ -23,8 +23,8 @@

-
- +
diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java index 0a28f64cf05..07b860025ce 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/config/IdentityProviderBootstrapTest.java @@ -22,8 +22,10 @@ import org.cloudfoundry.identity.uaa.provider.KeystoneIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; -import org.cloudfoundry.identity.uaa.provider.OauthIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.XOAuthIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.OidcAuthenticationFlow; import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; +import org.cloudfoundry.identity.uaa.provider.RawOauthAuthenticationFlow; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.saml.SamlIdentityProviderConfigurator; @@ -238,18 +240,18 @@ public void testRemovedKeystoneBootstrapIsInactive() throws Exception { @Test public void testRemovedOAuthIdentityProviderIsInactive() throws Exception { - OauthIdentityProviderDefinition oauthProvider = getOauthProviderDefinition(null); - OauthIdentityProviderDefinition oidcProvider = getOauthProviderDefinition("http://user.info.url"); + XOAuthIdentityProviderDefinition oauthProvider = getOauthProviderDefinition().setAuthenticationFlow(new RawOauthAuthenticationFlow()); + XOAuthIdentityProviderDefinition oidcProvider = getOauthProviderDefinition().setAuthenticationFlow(new OidcAuthenticationFlow()); IdentityProviderProvisioning provisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate); IdentityProviderBootstrap bootstrap = new IdentityProviderBootstrap(provisioning, new MockEnvironment()); - HashMap oauthProviderConfig = new HashMap<>(); + HashMap oauthProviderConfig = new HashMap<>(); oauthProviderConfig.put(OAUTH20, oauthProvider); oauthProviderConfig.put(OIDC10, oidcProvider); bootstrap.setOauthIdpDefintions(oauthProviderConfig); bootstrap.afterPropertiesSet(); - for (Map.Entry provider : oauthProviderConfig.entrySet()) { - IdentityProvider bootstrapOauthProvider = provisioning.retrieveByOrigin(provider.getKey(), IdentityZoneHolder.get().getId()); + for (Map.Entry provider : oauthProviderConfig.entrySet()) { + IdentityProvider bootstrapOauthProvider = provisioning.retrieveByOrigin(provider.getKey(), IdentityZoneHolder.get().getId()); assertNotNull(bootstrapOauthProvider); assertEquals(oauthProvider, bootstrapOauthProvider.getConfig()); assertNotNull(bootstrapOauthProvider.getCreated()); @@ -260,8 +262,8 @@ public void testRemovedOAuthIdentityProviderIsInactive() throws Exception { bootstrap.setOauthIdpDefintions(null); bootstrap.afterPropertiesSet(); - for (Map.Entry provider : oauthProviderConfig.entrySet()) { - IdentityProvider bootstrapOauthProvider = provisioning.retrieveByOrigin(provider.getKey(), IdentityZoneHolder.get().getId()); + for (Map.Entry provider : oauthProviderConfig.entrySet()) { + IdentityProvider bootstrapOauthProvider = provisioning.retrieveByOrigin(provider.getKey(), IdentityZoneHolder.get().getId()); assertNotNull(bootstrapOauthProvider); assertEquals(oauthProvider, bootstrapOauthProvider.getConfig()); assertNotNull(bootstrapOauthProvider.getCreated()); @@ -273,10 +275,10 @@ public void testRemovedOAuthIdentityProviderIsInactive() throws Exception { @Test(expected = IllegalArgumentException.class) public void bootstrap_failsIf_samlAndOauth_haveTheSameAlias() throws Exception { - OauthIdentityProviderDefinition oauthProvider = getOauthProviderDefinition(null); + XOAuthIdentityProviderDefinition oauthProvider = getOauthProviderDefinition(); IdentityProviderProvisioning provisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate); IdentityProviderBootstrap bootstrap = new IdentityProviderBootstrap(provisioning, new MockEnvironment()); - HashMap oauthProviderConfig = new HashMap<>(); + HashMap oauthProviderConfig = new HashMap<>(); oauthProviderConfig.put("same-alias", oauthProvider); SamlIdentityProviderDefinition definition = new SamlIdentityProviderDefinition(); @@ -295,8 +297,8 @@ public void bootstrap_failsIf_samlAndOauth_haveTheSameAlias() throws Exception { bootstrap.afterPropertiesSet(); } - protected OauthIdentityProviderDefinition getOauthProviderDefinition(String userInfoUrl) throws MalformedURLException { - return new OauthIdentityProviderDefinition() + protected XOAuthIdentityProviderDefinition getOauthProviderDefinition() throws MalformedURLException { + return new XOAuthIdentityProviderDefinition() .setAuthUrl(new URL("http://auth.url")) .setLinkText("link text") .setRelyingPartyId("relaying party id") @@ -304,8 +306,7 @@ protected OauthIdentityProviderDefinition getOauthProviderDefinition(String user .setShowLinkText(true) .setSkipSslValidation(true) .setTokenKey("key") - .setTokenKeyUrl(new URL("http://token.key.url")) - .setUserInfoUrl(userInfoUrl==null?null:new URL(userInfoUrl)); + .setTokenKeyUrl(new URL("http://token.key.url")); } @Test diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java index f7445fb004d..4b4a11b35a6 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/LoginInfoEndpointTests.java @@ -9,7 +9,9 @@ import org.cloudfoundry.identity.uaa.oauth.client.ClientConstants; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; -import org.cloudfoundry.identity.uaa.provider.OauthIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.XOAuthIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.OidcAuthenticationFlow; +import org.cloudfoundry.identity.uaa.provider.RawOauthAuthenticationFlow; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.saml.LoginSamlAuthenticationToken; @@ -517,12 +519,13 @@ public void testFilterIDPsForAuthcodeClientWithNoAllowedIDPsInOtherZone() throws @Test public void oauth_provider_links_shown() throws Exception { - OauthIdentityProviderDefinition definition = new OauthIdentityProviderDefinition(); + XOAuthIdentityProviderDefinition definition = new XOAuthIdentityProviderDefinition<>(); definition.setAuthUrl(new URL("http://auth.url")); definition.setTokenUrl(new URL("http://token.url")); + definition.setAuthenticationFlow(new RawOauthAuthenticationFlow()); - IdentityProvider identityProvider = MultitenancyFixture.identityProvider("oauth-idp-alias", "uaa"); + IdentityProvider identityProvider = MultitenancyFixture.identityProvider("oauth-idp-alias", "uaa"); identityProvider.setConfig(definition); when(identityProviderProvisioning.retrieveAll(anyBoolean(), anyString())).thenReturn(Collections.singletonList(identityProvider)); @@ -534,11 +537,12 @@ public void oauth_provider_links_shown() throws Exception { @Test public void passcode_prompt_present_whenThereIsAtleastOneActiveOauthProvider() throws Exception { - OauthIdentityProviderDefinition definition = new OauthIdentityProviderDefinition() + XOAuthIdentityProviderDefinition definition = new XOAuthIdentityProviderDefinition() .setAuthUrl(new URL("http://auth.url")) - .setTokenUrl(new URL("http://token.url")); + .setTokenUrl(new URL("http://token.url")) + .setAuthenticationFlow(new RawOauthAuthenticationFlow()); - IdentityProvider identityProvider = MultitenancyFixture.identityProvider("oauth-idp-alias", "uaa"); + IdentityProvider identityProvider = MultitenancyFixture.identityProvider("oauth-idp-alias", "uaa"); identityProvider.setConfig(definition); when(identityProviderProvisioning.retrieveAll(anyBoolean(), anyString())).thenReturn(Collections.singletonList(identityProvider)); @@ -552,18 +556,19 @@ public void passcode_prompt_present_whenThereIsAtleastOneActiveOauthProvider() t @Test public void we_return_both_oauth_and_oidc_providers() throws Exception { - OauthIdentityProviderDefinition oauthDefinition = new OauthIdentityProviderDefinition() + XOAuthIdentityProviderDefinition oauthDefinition = new XOAuthIdentityProviderDefinition() + .setAuthenticationFlow(new RawOauthAuthenticationFlow()) .setAuthUrl(new URL("http://auth.url")) .setTokenUrl(new URL("http://token.url")); - OauthIdentityProviderDefinition oidcDefinition = new OauthIdentityProviderDefinition() + XOAuthIdentityProviderDefinition oidcDefinition = new XOAuthIdentityProviderDefinition() + .setAuthenticationFlow(new OidcAuthenticationFlow()) .setAuthUrl(new URL("http://auth.url")) - .setTokenUrl(new URL("http://token.url")) - .setUserInfoUrl(new URL("http://user.info.url")); + .setTokenUrl(new URL("http://token.url")); - IdentityProvider oauthProvider = MultitenancyFixture.identityProvider("oauth-idp-alias", "uaa"); + IdentityProvider oauthProvider = MultitenancyFixture.identityProvider("oauth-idp-alias", "uaa"); oauthProvider.setConfig(oauthDefinition); - IdentityProvider oidcProvider = MultitenancyFixture.identityProvider("oidc-idp-alias", "uaa"); + IdentityProvider oidcProvider = MultitenancyFixture.identityProvider("oidc-idp-alias", "uaa"); oidcProvider.setConfig(oidcDefinition); when(identityProviderProvisioning.retrieveAll(anyBoolean(), anyString())).thenReturn(Arrays.asList(oauthProvider, oidcProvider)); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/XOAuthAuthenticationFilterTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/XOAuthAuthenticationFilterTest.java new file mode 100644 index 00000000000..186d4fb75a6 --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/XOAuthAuthenticationFilterTest.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * 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.oauth; + + +import org.cloudfoundry.identity.uaa.test.MockAuthentication; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.core.context.SecurityContextHolder; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyObject; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class XOAuthAuthenticationFilterTest { + + @Test + public void getXOAuthCodeTokenFromRequest() throws Exception { + XOAuthAuthenticationManager xOAuthAuthenticationManager = Mockito.mock(XOAuthAuthenticationManager.class); + XOAuthAuthenticationFilter filter = new XOAuthAuthenticationFilter(xOAuthAuthenticationManager); + + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRequestURL()).thenReturn(new StringBuffer("http://localhost/uaa/login/callback/the_origin")); + when(request.getServletPath()).thenReturn("/login/callback/the_origin"); + when(request.getParameter("code")).thenReturn("the_code"); + + MockAuthentication authentication = new MockAuthentication(); + Mockito.when(xOAuthAuthenticationManager.authenticate(anyObject())).thenReturn(authentication); + + FilterChain chain = mock(FilterChain.class); + MockHttpServletResponse response = new MockHttpServletResponse(); + filter.doFilter(request, response, chain); + + ArgumentCaptor captor = ArgumentCaptor.forClass(XOAuthCodeToken.class); + verify(xOAuthAuthenticationManager).authenticate(captor.capture()); + verify(chain).doFilter(request, response); + + XOAuthCodeToken xoAuthCodeToken = captor.getValue(); + assertEquals("the_code", xoAuthCodeToken.getCode()); + assertEquals("the_origin", xoAuthCodeToken.getOrigin()); + assertEquals("http://localhost/uaa/login/callback/the_origin", xoAuthCodeToken.getRedirectUrl()); + assertEquals(authentication, SecurityContextHolder.getContext().getAuthentication()); + } + +} \ No newline at end of file diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/XOAuthAuthenticationManagerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/XOAuthAuthenticationManagerTest.java new file mode 100644 index 00000000000..e49aeef975f --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/XOAuthAuthenticationManagerTest.java @@ -0,0 +1,125 @@ +/******************************************************************************* + * 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.oauth; + +import org.cloudfoundry.identity.uaa.account.UserInfoResponse; +import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; +import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.oauth.token.CompositeAccessToken; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.OidcAuthenticationFlow; +import org.cloudfoundry.identity.uaa.provider.XOAuthIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.user.UaaUser; +import org.cloudfoundry.identity.uaa.util.JsonUtils; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.test.web.client.response.MockRestResponseCreators; + +import java.net.URL; + +import static junit.framework.TestCase.assertEquals; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.springframework.http.HttpStatus.OK; +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.header; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; + +public class XOAuthAuthenticationManagerTest { + + private MockRestServiceServer mockUaaServer; + private XOAuthAuthenticationManager xoAuthAuthenticationManager; + private IdentityProviderProvisioning provisioning; + + private String idTokenJwt = "eyJhbGciOiJIUzI1NiIsImtpZCI6InRlc3RLZXkiLCJ0eXAiOiJKV1QifQ." + + "eyJzdWIiOiIxMjM0NSIsInVzZXJfbmFtZSI6Im1hcmlzc2EiLCJvcmlnaW4iOiJ1YWEiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvdWFhL29hdXRoL3Rva2VuIiwiZ2l2ZW5fbmFtZSI6Ik1hcmlzc2EiLCJjbGllbnRfaWQiOiJjbGllbnQiLCJhdWQiOlsiY2xpZW50Il0sInppZCI6InVhYSIsInVzZXJfaWQiOiIxMjM0NSIsImF6cCI6ImNsaWVudCIsInNjb3BlIjpbIm9wZW5pZCJdLCJhdXRoX3RpbWUiOjE0NTg2MDM5MTMsInBob25lX251bWJlciI6IjEyMzQ1Njc4OTAiLCJleHAiOjE0NTg2NDcxMTMsImlhdCI6MTQ1ODYwMzkxMywiZmFtaWx5X25hbWUiOiJCbG9nZ3MiLCJqdGkiOiJiMjNmZTE4My0xNThkLTRhZGMtOGFmZi02NWM0NDBiYmJlZTEiLCJlbWFpbCI6Im1hcmlzc2FAYmxvZ2dzLmNvbSIsInJldl9zaWciOiIzMzE0ZGM5OCIsImNpZCI6ImNsaWVudCJ9" + + ".UCl_8gJMlZWBACYefXCRkZqDi72gZ6g-HJCvvNcQUFc"; + + @Before + public void setUp() { + provisioning = Mockito.mock(JdbcIdentityProviderProvisioning.class); + xoAuthAuthenticationManager = new XOAuthAuthenticationManager(provisioning); + mockUaaServer = MockRestServiceServer.createServer(xoAuthAuthenticationManager.getRestTemplate()); + } + + @Test + public void authenticatesWithExternalCode() throws Exception { + String origin = "the_origin"; + String code = "the_code"; + XOAuthCodeToken xCodeToken = new XOAuthCodeToken(code, origin, "http://localhost/callback/the_origin"); + + IdentityProvider identityProvider = new IdentityProvider<>(); + identityProvider.setName("my oidc provider"); + identityProvider.setIdentityZoneId(OriginKeys.UAA); + XOAuthIdentityProviderDefinition config = new XOAuthIdentityProviderDefinition<>(); + config.setAuthUrl(new URL("http://oidc10.identity.cf-app.com/oauth/authorize")); + config.setTokenUrl(new URL("http://oidc10.identity.cf-app.com/oauth/token")); + config.setTokenKeyUrl(new URL("http://oidc10.identity.cf-app.com/token_key")); + config.setShowLinkText(true); + config.setLinkText("My OIDC Provider"); + config.setSkipSslValidation(true); + config.setRelyingPartyId("identity"); + config.setRelyingPartySecret("identitysecret"); + config.setAuthenticationFlow(new OidcAuthenticationFlow().setUserInfoUrl(new URL("http://oidc10.identity.cf-app.com/userinfo"))); + identityProvider.setConfig(config); + identityProvider.setOriginKey("puppy"); + + Mockito.when(provisioning.retrieveByOrigin(eq(origin), anyString())).thenReturn(identityProvider); + + CompositeAccessToken compositeAccessToken = new CompositeAccessToken("accessToken"); + compositeAccessToken.setIdTokenValue(idTokenJwt); + String response = JsonUtils.writeValueAsString(compositeAccessToken); + mockUaaServer.expect(requestTo("http://oidc10.identity.cf-app.com/oauth/token")).andRespond(withStatus(OK).contentType(APPLICATION_JSON).body(response)); + UserInfoResponse userInfoResponse = new UserInfoResponse(); + userInfoResponse.setEmail("marissa@bloggs.com"); + userInfoResponse.setFamilyName("Bloggs"); + userInfoResponse.setGivenName("Marissa"); + userInfoResponse.setUsername("marissa"); + mockUaaServer.expect(requestTo("http://oidc10.identity.cf-app.com/userinfo")).andExpect(header("Authorization", "bearer " + idTokenJwt)).andRespond(withStatus(OK).body(JsonUtils.writeValueAsString(userInfoResponse)).contentType(APPLICATION_JSON)); + + Authentication authentication = xoAuthAuthenticationManager.authenticate(xCodeToken); + + mockUaaServer.verify(); + + assertThat(authentication, notNullValue()); + assertThat(authentication, instanceOf(UaaAuthentication.class)); + + UaaAuthentication uaaAuthentication = (UaaAuthentication) authentication; + + assertEquals("marissa", uaaAuthentication.getName()); + assertThat(uaaAuthentication.getPrincipal(), instanceOf(UaaPrincipal.class)); + + UaaPrincipal principal = uaaAuthentication.getPrincipal(); + assertEquals("marissa@bloggs.com", principal.getEmail()); + assertEquals("the_origin", principal.getOrigin()); + assertEquals(OriginKeys.UAA, principal.getZoneId()); + } + + @Test + public void failsIfProviderIsNotOIDCOrOAuth() throws Exception { + + + } +} \ No newline at end of file diff --git a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml index 3e25240a5e9..2414b2b31ca 100755 --- a/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml +++ b/uaa/src/main/webapp/WEB-INF/spring/oauth-endpoints.xml @@ -256,6 +256,34 @@ + + + + + + + + + + + + + + + + + GET + + + + + + + + + + diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OIDCLoginIT.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OIDCLoginIT.java new file mode 100644 index 00000000000..2c0b9ce2069 --- /dev/null +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/OIDCLoginIT.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * 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.integration.feature; + +import org.cloudfoundry.identity.uaa.ServerRunning; +import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils; +import org.cloudfoundry.identity.uaa.integration.util.ScreenshotOnFail; +import org.cloudfoundry.identity.uaa.login.test.LoginServerClassRunner; +import org.cloudfoundry.identity.uaa.provider.IdentityProvider; +import org.cloudfoundry.identity.uaa.provider.OidcAuthenticationFlow; +import org.cloudfoundry.identity.uaa.provider.RawOauthAuthenticationFlow; +import org.cloudfoundry.identity.uaa.provider.XOAuthIdentityProviderDefinition; +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.oauth2.client.test.TestAccounts; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.web.client.RestOperations; + +import java.net.URL; + +import static org.junit.Assert.assertThat; + +@RunWith(LoginServerClassRunner.class) +@ContextConfiguration(classes = DefaultIntegrationTestConfig.class) +public class OIDCLoginIT { + + @Autowired @Rule + public IntegrationTestRule integrationTestRule; + + @Rule + public ScreenshotOnFail screenShootRule = new ScreenshotOnFail(); + + @Autowired + RestOperations restOperations; + + @Autowired + WebDriver webDriver; + + @Value("${integration.test.base_url}") + String baseUrl; + + @Autowired + TestAccounts testAccounts; + + @Autowired + TestClient testClient; + + @Test + public void successfulLoginWithOIDCProvider() throws Exception { + String clientCredentialsToken = IntegrationTestUtils.getClientCredentialsToken(baseUrl, "admin", "adminsecret"); + + IdentityProvider identityProvider = new IdentityProvider<>(); + identityProvider.setName("my oidc provider"); + identityProvider.setIdentityZoneId(OriginKeys.UAA); + XOAuthIdentityProviderDefinition config = new XOAuthIdentityProviderDefinition<>(); + config.setAuthUrl(new URL("http://oidc10.identity.cf-app.com/oauth/authorize")); + config.setTokenUrl(new URL("http://oidc10.identity.cf-app.com/oauth/token")); + config.setTokenKeyUrl(new URL("http://oidc10.identity.cf-app.com/token_key")); + config.setShowLinkText(true); + config.setLinkText("My OIDC Provider"); + config.setSkipSslValidation(true); + config.setRelyingPartyId("identity"); + config.setRelyingPartySecret("identitysecret"); + config.setAuthenticationFlow(new OidcAuthenticationFlow()); + identityProvider.setConfig(config); + identityProvider.setOriginKey("puppy"); + + IntegrationTestUtils.createOrUpdateProvider(clientCredentialsToken, baseUrl, identityProvider); + + webDriver.get(baseUrl + "/login"); + webDriver.findElement(By.linkText("My OIDC Provider")).click(); + Assert.assertThat(webDriver.getCurrentUrl(), Matchers.containsString("oidc10.identity.cf-app.com")); + + webDriver.findElement(By.name("username")).sendKeys("marissa"); + webDriver.findElement(By.name("password")).sendKeys("koala"); + webDriver.findElement(By.xpath("//input[@value='Sign in']")).click(); + + Assert.assertThat(webDriver.getCurrentUrl(), Matchers.containsString("localhost")); + assertThat(webDriver.findElement(By.cssSelector("h1")).getText(), Matchers.containsString("Where to?")); + } +} diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java index 9c1bc3e4a03..fc77f1f2d52 100755 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/BootstrapTests.java @@ -29,7 +29,9 @@ import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; -import org.cloudfoundry.identity.uaa.provider.OauthIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.OidcAuthenticationFlow; +import org.cloudfoundry.identity.uaa.provider.RawOauthAuthenticationFlow; +import org.cloudfoundry.identity.uaa.provider.XOAuthIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.PasswordPolicy; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; @@ -75,8 +77,6 @@ import java.io.File; import java.io.IOException; import java.lang.reflect.Field; -import java.math.BigInteger; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Collections; @@ -337,12 +337,11 @@ public void testPropertyValuesWhenSetInYaml() throws Exception { assertTrue(uaaIdp.getConfig().isDisableInternalUserManagement()); assertFalse(uaaIdp.isActive()); - IdentityProvider oauthProvider = idpProvisioning.retrieveByOrigin("my-oauth-provider", IdentityZone.getUaa().getId()); + IdentityProvider> oauthProvider = idpProvisioning.retrieveByOrigin("my-oauth-provider", IdentityZone.getUaa().getId()); assertNotNull(oauthProvider); assertEquals("http://my-auth.com", oauthProvider.getConfig().getAuthUrl().toString()); assertEquals("http://my-token.com", oauthProvider.getConfig().getTokenUrl().toString()); assertEquals("my-token-key", oauthProvider.getConfig().getTokenKey()); - assertNull(oauthProvider.getConfig().getUserInfoUrl()); assertEquals(true, oauthProvider.getConfig().isShowLinkText()); assertEquals("uaa", oauthProvider.getConfig().getRelyingPartyId()); assertEquals("secret", oauthProvider.getConfig().getRelyingPartySecret()); @@ -351,11 +350,10 @@ public void testPropertyValuesWhenSetInYaml() throws Exception { assertEquals("Bloggs", oauthProvider.getConfig().getAttributeMappings().get(FAMILY_NAME_ATTRIBUTE_NAME)); assertEquals(OAUTH20, oauthProvider.getType()); - IdentityProvider oidcProvider = idpProvisioning.retrieveByOrigin("my-oidc-provider", IdentityZone.getUaa().getId()); + IdentityProvider> oidcProvider = idpProvisioning.retrieveByOrigin("my-oidc-provider", IdentityZone.getUaa().getId()); assertNotNull(oidcProvider); assertEquals("http://my-auth.com", oidcProvider.getConfig().getAuthUrl().toString()); assertEquals("http://my-token.com", oidcProvider.getConfig().getTokenUrl().toString()); - assertEquals("http://my-token.com/userinfo", oidcProvider.getConfig().getUserInfoUrl().toString()); assertEquals("my-token-key", oidcProvider.getConfig().getTokenKey()); assertEquals(true, oidcProvider.getConfig().isShowLinkText()); assertEquals("uaa", oidcProvider.getConfig().getRelyingPartyId()); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java index 515121f4b25..90f8a33d7b1 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/LoginMockMvcTests.java @@ -22,7 +22,7 @@ import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.LockoutPolicy; -import org.cloudfoundry.identity.uaa.provider.OauthIdentityProviderDefinition; +import org.cloudfoundry.identity.uaa.provider.XOAuthIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.SamlIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.saml.IdentityProviderConfiguratorTests; @@ -975,13 +975,13 @@ public void noSamlRedirect_ifOauthProviderAlsoPresent() throws Exception { activeIdentityProvider.setOriginKey(alias); activeIdentityProvider = mockMvcUtils.createIdpUsingWebRequest(getMockMvc(), identityZone.getId(), zoneAdminToken, activeIdentityProvider, status().isCreated()); - OauthIdentityProviderDefinition definition = new OauthIdentityProviderDefinition(); + XOAuthIdentityProviderDefinition definition = new XOAuthIdentityProviderDefinition(); definition.setAuthUrl(new URL("http://auth.url")); definition.setTokenUrl(new URL("http://token.url")); String oauthAlias = "login-oauth-" + generator.generate(); - IdentityProvider oauthIdentityProvider = MultitenancyFixture.identityProvider(oauthAlias, "uaa"); + IdentityProvider oauthIdentityProvider = MultitenancyFixture.identityProvider(oauthAlias, "uaa"); oauthIdentityProvider.setConfig(definition); oauthIdentityProvider.setActive(true); @@ -1529,6 +1529,33 @@ public void autologin_with_validCode_RedirectsToHome() throws Exception { .andExpect(redirectedUrl("home")); } + @Test + public void oidcCallbackEndpoint_handlesAuthorizationCode_Successfully() throws Exception { + IdentityProviderProvisioning identityProviderProvisioning = getWebApplicationContext().getBean(IdentityProviderProvisioning.class); + IdentityProvider identityProvider = new IdentityProvider(); + identityProvider.setName("my oidc provider"); + identityProvider.setIdentityZoneId(OriginKeys.UAA); + XOAuthIdentityProviderDefinition config = new XOAuthIdentityProviderDefinition(); + config.setAuthUrl(new URL("http://oidc10.identity.cf-app.com/oauth/authorize")); + config.setTokenUrl(new URL("http://oidc10.identity.cf-app.com/oauth/token")); + config.setTokenKeyUrl(new URL("http://oidc10.identity.cf-app.com/token_key")); + config.setShowLinkText(true); + config.setLinkText("My OIDC Provider"); + config.setSkipSslValidation(true); + config.setRelyingPartyId("identity"); + config.setRelyingPartySecret("identitysecret"); + identityProvider.setConfig(config); + identityProvider.setOriginKey("puppy"); + + identityProviderProvisioning.create(identityProvider); + + getMockMvc().perform(get("/login/callback/puppy") + .param("code", "the_code")) + .andExpect(view().name("home")); + + + } + private void changeLockoutPolicyForIdpInZone(IdentityZone zone) throws Exception { IdentityProviderProvisioning identityProviderProvisioning = getWebApplicationContext().getBean(IdentityProviderProvisioning.class); IdentityProvider identityProvider = identityProviderProvisioning.retrieveByOrigin(OriginKeys.UAA, zone.getId());