Skip to content

Commit

Permalink
Support authorization_code using a token as authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
fhanik committed Nov 30, 2015
1 parent 6d7151b commit 4c134f3
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 4 deletions.
Expand Up @@ -26,12 +26,14 @@
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
import org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeResourceDetails;
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider;
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordResourceDetails;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.HttpMessageConverterExtractor;
import org.springframework.web.client.ResponseExtractor;
Expand All @@ -40,6 +42,8 @@
import java.net.URI;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE;
Expand Down Expand Up @@ -127,6 +131,7 @@ public UaaContext authenticate(TokenRequest request) {
case CLIENT_CREDENTIALS: return authenticateClientCredentials(request);
case PASSWORD: return authenticatePassword(request);
case AUTHORIZATION_CODE: return authenticateAuthCode(request);
case AUTHORIZATION_CODE_WITH_TOKEN: return authenticateAuthCodeWithToken(request);
default: throw new UnsupportedGrantTypeException("Not implemented:"+request.getGrantType());
}
}
Expand All @@ -147,6 +152,35 @@ protected UaaContext authenticateAuthCode(final TokenRequest tokenRequest) {
throw new UnsupportedOperationException(AUTHORIZATION_CODE +" is not yet implemented");
}

protected UaaContext authenticateAuthCodeWithToken(final TokenRequest tokenRequest) {
AuthorizationCodeAccessTokenProvider provider = new AuthorizationCodeAccessTokenProvider() {
@Override
protected ResponseExtractor<OAuth2AccessToken> getResponseExtractor() {
getRestTemplate(); // force initialization
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
return new HttpMessageConverterExtractor<OAuth2AccessToken>(CompositeAccessToken.class, Arrays.asList(converter));
}
};
enhanceForIdTokenRetrieval(tokenRequest, provider);
AuthorizationCodeResourceDetails details = new AuthorizationCodeResourceDetails();
details.setPreEstablishedRedirectUri(tokenRequest.getRedirectUriRedirectUri().toString());
configureResourceDetails(tokenRequest, details);
setClientCredentials(tokenRequest, details);
setRequestScopes(tokenRequest, details);
details.setUserAuthorizationUri(tokenRequest.getAuthorizationEndpoint().toString());
DefaultOAuth2ClientContext oAuth2ClientContext = new DefaultOAuth2ClientContext();
String state = new RandomValueStringGenerator().generate();
oAuth2ClientContext.getAccessTokenRequest().setStateKey(state);
oAuth2ClientContext.setPreservedState(state, details.getPreEstablishedRedirectUri());
oAuth2ClientContext.getAccessTokenRequest().setCurrentUri(details.getPreEstablishedRedirectUri());
Map<String, List<String>> headers = (Map<String, List<String>>) oAuth2ClientContext.getAccessTokenRequest().getHeaders();
headers.put("Authorization", Arrays.asList("bearer " + tokenRequest.getAuthCodeAPIToken()));
OAuth2RestTemplate template = new OAuth2RestTemplate(details, oAuth2ClientContext);
template.setAccessTokenProvider(provider);
OAuth2AccessToken token = template.getAccessToken();
return new UaaContextImpl(tokenRequest, template, (CompositeAccessToken) token);
}


/**
* Performs a {@link org.cloudfoundry.identity.client.token.GrantType#PASSWORD authentication}
Expand Down
Expand Up @@ -22,5 +22,6 @@ public enum GrantType {
PASSWORD,
IMPLICIT,
AUTHORIZATION_CODE,
AUTHORIZATION_CODE_WITH_TOKEN,
REFRESH_TOKEN
}
Expand Up @@ -40,6 +40,7 @@ public class TokenRequest {
private URI authorizationEndpoint;
private boolean idToken = false;
private URI redirectUri;
private String authCodeAPIToken;

/**
* Constructs a token request
Expand Down Expand Up @@ -90,6 +91,19 @@ public boolean isValid() {
redirectUri
)
);
case AUTHORIZATION_CODE_WITH_TOKEN:
return !hasAnyNullValues(
Arrays.asList(
tokenEndpoint,
authorizationEndpoint,
clientId,
clientSecret,
username,
password,
redirectUri,
authCodeAPIToken
)
);
default: return false;
}
}
Expand Down Expand Up @@ -273,6 +287,26 @@ public TokenRequest setRedirectUri(URI redirectUri) {
return this;
}

/**
* Returns the UAA token that will be used if this token request is an
* {@link GrantType#AUTHORIZATION_CODE_WITH_TOKEN} grant.
* @return the token set or null if not set
*/
public String getAuthCodeAPIToken() {
return authCodeAPIToken;
}

/**
* Sets the token used as authentication mechanism when using
* the {@link GrantType#AUTHORIZATION_CODE_WITH_TOKEN} grant.
* @param authCodeAPIToken - a valid UAA token
* @return this mutable object
*/
public TokenRequest setAuthCodeAPIToken(String authCodeAPIToken) {
this.authCodeAPIToken = authCodeAPIToken;
return this;
}

/**
* Returns true if the list or any item in the list is null
* @param objects a list of items to be evaluated for null references
Expand Down
Expand Up @@ -24,8 +24,10 @@
import org.junit.Test;

import java.net.URI;
import java.util.Arrays;

import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE;
import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE_WITH_TOKEN;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
Expand Down Expand Up @@ -61,19 +63,23 @@ public void test_admin_client_token() throws Exception {

@Test
public void test_password_token_without_id_token() throws Exception {
UaaContext context = retrievePasswordToken(null);
assertTrue(context.getToken().getScope().contains("openid"));
}

protected UaaContext retrievePasswordToken(String scopes) {
TokenRequest passwordGrant = factory.tokenRequest()
.setClientId("cf")
.setClientSecret("")
.setGrantType(GrantType.PASSWORD)
.setUsername("marissa")
.setPassword("koala");

UaaContext context = factory.authenticate(passwordGrant);
assertNotNull(context);
assertTrue(context.hasAccessToken());
assertFalse(context.hasIdToken());
assertTrue(context.hasRefreshToken());
assertTrue(context.getToken().getScope().contains("openid"));
return context;
}

@Test
Expand Down Expand Up @@ -131,4 +137,26 @@ public void test_auth_code_token_without_id_token() throws Exception {
assertTrue(context.getToken().getScope().contains("openid"));
}

@Test
public void test_auth_code_token_using_api() throws Exception {
UaaContext passwordContext = retrievePasswordToken("uaa.user");
assertTrue(passwordContext.getToken().getScope().contains("uaa.user"));
TokenRequest authorizationCode = factory.tokenRequest()
.setGrantType(AUTHORIZATION_CODE_WITH_TOKEN)
.setRedirectUri(new URI("http://localhost:8080/app/"))
.setClientId("app")
.setClientSecret("appclientsecret")
.setUsername("marissa")
.setPassword("koala")
.setScopes(Arrays.asList("openid"))
.setAuthCodeAPIToken(passwordContext.getToken().getValue());
UaaContext context = factory.authenticate(authorizationCode);
assertNotNull(context);
assertTrue(context.hasAccessToken());
//we receive an id_token because we request 'openid' explicitly
assertTrue(context.hasIdToken());
assertTrue(context.hasRefreshToken());
assertTrue(context.getToken().getScope().contains("openid"));
}

}
Expand Up @@ -22,6 +22,7 @@

import static java.util.Collections.EMPTY_LIST;
import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE;
import static org.cloudfoundry.identity.client.token.GrantType.AUTHORIZATION_CODE_WITH_TOKEN;
import static org.cloudfoundry.identity.client.token.GrantType.CLIENT_CREDENTIALS;
import static org.cloudfoundry.identity.client.token.GrantType.PASSWORD;
import static org.junit.Assert.assertFalse;
Expand Down Expand Up @@ -68,6 +69,18 @@ public void test_is_auth_code_grant_valid() throws Exception {
assertTrue(request.setRedirectUri(new URI("http://localhost:8080/test")).isValid());
}

@Test
public void test_is_auth_code_grant_api_valid() throws Exception {
assertFalse(request.isValid());
assertFalse(request.setGrantType(AUTHORIZATION_CODE_WITH_TOKEN).isValid());
assertFalse(request.setClientId("client_id").isValid());
assertFalse(request.setClientSecret("client_secret").isValid());
assertFalse(request.setUsername("username").isValid());
assertFalse(request.setPassword("password").isValid());
assertFalse(request.setAuthCodeAPIToken("some token").isValid());
assertTrue(request.setRedirectUri(new URI("http://localhost:8080/test")).isValid());
}


@Test
public void test_is_null_function() {
Expand Down
4 changes: 2 additions & 2 deletions docs/UAA-APIs.rst
Expand Up @@ -47,7 +47,7 @@ Here is a summary of the different scopes that are known to the UAA.
* **approvals.me** - not currently used
* **openid** - Required to access the /userinfo endpoint. Intended for OpenID clients.
* **groups.update** - Allows a group to be updated. Can also be accomplished with ``scim.write``
* **uaa.user** - scope to indicate this is a user
* **uaa.user** - scope to indicate this is a user, also required in the token if using `API Authorization Requests Code: ``GET /oauth/authorize`` (non standard /oauth/authorize)`_
* **uaa.resource** - scope to indicate this is a resource server, used for the /check_token endpoint
* **uaa.admin** - scope to indicate this is the super user
* **uaa.none** - scope to indicate that this client will not be performing actions on behalf of a user
Expand Down Expand Up @@ -755,7 +755,7 @@ Fields *Available Fields* ::
===================== ==================== ======== ========================================================================================================================================================================
accessTokenValidity int Optional How long the access token is valid for in seconds.
refreshTokenValidity int Optional How long the refresh token is valid for seconds.

SAML Identity Provider Configuration ``SamlConfig`` (part of Identity Zone Configuration - See class org.cloudfoundry.identity.uaa.zone.SamlConfig)
===================== ==================== ======== ========================================================================================================================================================================
requestSigned Boolean Optional Exposed SAML metadata property. If ``true``, the service provider will sign all outgoing authentication requests. Defaults to ``false``.
Expand Down

0 comments on commit 4c134f3

Please sign in to comment.