Skip to content

Commit

Permalink
Populate id token with user information if scope 'profile' is requested
Browse files Browse the repository at this point in the history
[#99446736] https://www.pivotaltracker.com/story/show/99446736

Signed-off-by: Madhura Bhave <mbhave@pivotal.io>
  • Loading branch information
Paul Warren authored and mbhave committed Oct 13, 2015
1 parent 8076522 commit a501a40
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 58 deletions.
Expand Up @@ -28,6 +28,7 @@ public class Claims {
public static final String NAME = "name";
public static final String GIVEN_NAME = "given_name";
public static final String FAMILY_NAME = "family_name";
public static final String PHONE_NUMBER = "phone_number";
public static final String EMAIL = "email";
public static final String CLIENT_ID = "client_id";
public static final String EXP = "exp";
Expand All @@ -48,4 +49,5 @@ public class Claims {
public static final String NONCE = "nonce";
public static final String ORIGIN = "origin";
public static final String ROLES = "roles";
public static final String PROFILE = "profile";
}
Expand Up @@ -92,19 +92,23 @@
import static org.cloudfoundry.identity.uaa.oauth.Claims.CLIENT_ID;
import static org.cloudfoundry.identity.uaa.oauth.Claims.EMAIL;
import static org.cloudfoundry.identity.uaa.oauth.Claims.EXP;
import static org.cloudfoundry.identity.uaa.oauth.Claims.FAMILY_NAME;
import static org.cloudfoundry.identity.uaa.oauth.Claims.GIVEN_NAME;
import static org.cloudfoundry.identity.uaa.oauth.Claims.GRANT_TYPE;
import static org.cloudfoundry.identity.uaa.oauth.Claims.IAT;
import static org.cloudfoundry.identity.uaa.oauth.Claims.ISS;
import static org.cloudfoundry.identity.uaa.oauth.Claims.JTI;
import static org.cloudfoundry.identity.uaa.oauth.Claims.NONCE;
import static org.cloudfoundry.identity.uaa.oauth.Claims.ORIGIN;
import static org.cloudfoundry.identity.uaa.oauth.Claims.PHONE_NUMBER;
import static org.cloudfoundry.identity.uaa.oauth.Claims.REVOCATION_SIGNATURE;
import static org.cloudfoundry.identity.uaa.oauth.Claims.ROLES;
import static org.cloudfoundry.identity.uaa.oauth.Claims.SCOPE;
import static org.cloudfoundry.identity.uaa.oauth.Claims.SUB;
import static org.cloudfoundry.identity.uaa.oauth.Claims.USER_ID;
import static org.cloudfoundry.identity.uaa.oauth.Claims.USER_NAME;
import static org.cloudfoundry.identity.uaa.oauth.Claims.ZONE_ID;
import static org.cloudfoundry.identity.uaa.oauth.Claims.PROFILE;


/**
Expand Down Expand Up @@ -243,9 +247,7 @@ public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenReque
OAuth2AccessToken accessToken =
createAccessToken(
user.getId(),
user.getOrigin(),
user.getUsername(),
user.getEmail(),
user,
claims.get(AUTH_TIME) != null ? new Date(((Long)claims.get(AUTH_TIME)) * 1000l) : null,
validity != null ? validity.intValue() : accessTokenValiditySeconds,
null,
Expand Down Expand Up @@ -306,9 +308,7 @@ private void checkForApproval(String userid,


private OAuth2AccessToken createAccessToken(String userId,
String origin,
String username,
String userEmail,
UaaUser user,
Date userAuthenticationTime,
int validitySeconds,
Collection<GrantedAuthority> clientScopes,
Expand Down Expand Up @@ -351,9 +351,7 @@ private OAuth2AccessToken createAccessToken(String userId,
Map<String, ?> jwtAccessToken = createJWTAccessToken(
accessToken,
userId,
origin,
username,
userEmail,
user,
userAuthenticationTime,
clientScopes,
requestedScopes,
Expand All @@ -371,7 +369,7 @@ private OAuth2AccessToken createAccessToken(String userId,
String token = JwtHelper.encode(content, signerProvider.getSigner()).getEncoded();
// This setter copies the value and returns. Don't change.
accessToken.setValue(token);
populateIdToken(accessToken, jwtAccessToken, requestedScopes, responseTypes, clientId, forceIdTokenCreation, externalGroupsForIdToken);
populateIdToken(accessToken, jwtAccessToken, requestedScopes, responseTypes, clientId, forceIdTokenCreation, externalGroupsForIdToken, user);
publish(new TokenIssuedEvent(accessToken, SecurityContextHolder.getContext().getAuthentication()));

return accessToken;
Expand All @@ -383,7 +381,8 @@ private void populateIdToken(OpenIdToken token,
Set<String> responseTypes,
String aud,
boolean forceIdTokenCreation,
Set<String> externalGroupsForIdToken) {
Set<String> externalGroupsForIdToken,
UaaUser user) {
if (forceIdTokenCreation || (scopes.contains("openid") && responseTypes.contains(OpenIdToken.ID_TOKEN))) {
try {
Map<String, Object> clone = new HashMap<>(accessTokenValues);
Expand All @@ -397,10 +396,21 @@ private void populateIdToken(OpenIdToken token,
clone.put(SCOPE, idTokenScopes);
clone.put(AUD, new HashSet(Arrays.asList(aud)));

if (scopes.contains(ROLES) && !externalGroupsForIdToken.isEmpty()) {
if (scopes.contains(ROLES) && (externalGroupsForIdToken != null && !externalGroupsForIdToken.isEmpty())) {
clone.put(ROLES, externalGroupsForIdToken);
}

if(scopes.contains(PROFILE) && user != null) {
String givenName = user.getGivenName();
if(givenName != null) clone.put(GIVEN_NAME, givenName);

String familyName = user.getFamilyName();
if(familyName != null) clone.put(FAMILY_NAME, familyName);

String phoneNumber = user.getPhoneNumber();
if(phoneNumber != null) clone.put(PHONE_NUMBER, phoneNumber);
}

String content = JsonUtils.writeValueAsString(clone);
String encoded = JwtHelper.encode(content, signerProvider.getSigner()).getEncoded();
token.setIdTokenValue(encoded);
Expand All @@ -412,9 +422,7 @@ private void populateIdToken(OpenIdToken token,

private Map<String, ?> createJWTAccessToken(OAuth2AccessToken token,
String userId,
String origin,
String username,
String userEmail,
UaaUser user,
Date userAuthenticationTime,
Collection<GrantedAuthority> clientScopes,
Set<String> requestedScopes,
Expand Down Expand Up @@ -444,10 +452,17 @@ private void populateIdToken(OpenIdToken token,
}
if (!"client_credentials".equals(grantType)) {
response.put(USER_ID, userId);
response.put(ORIGIN, origin);
response.put(USER_NAME, username == null ? userId : username);
if (null != userEmail) {
response.put(EMAIL, userEmail);
if (user != null) {
String origin = user.getOrigin();
if (StringUtils.hasLength(origin)) {
response.put(ORIGIN, origin);
}
String username = user.getUsername();
response.put(USER_NAME, username == null ? userId : username);
String userEmail = user.getEmail();
if (userEmail != null) {
response.put(EMAIL, userEmail);
}
}
if (userAuthenticationTime!=null) {
response.put(AUTH_TIME, userAuthenticationTime.getTime() / 1000);
Expand Down Expand Up @@ -478,11 +493,7 @@ private void populateIdToken(OpenIdToken token,
@Override
public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {


String origin = null;
String userId = null;
String username = null;
String userEmail = null;
String userId;
Date userAuthenticationTime = null;
UaaUser user = null;
boolean wasIdTokenRequestedThroughAuthCodeScopeParameter = false;
Expand All @@ -495,15 +506,11 @@ public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication)
} else {
userId = getUserId(authentication);
user = userDatabase.retrieveUserById(userId);
origin = user.getOrigin();
username = user.getUsername();
userEmail = user.getEmail();
if (authentication.getUserAuthentication() instanceof UaaAuthentication) {
userAuthenticationTime = new Date(((UaaAuthentication)authentication.getUserAuthentication()).getAuthenticatedTime());
}
}


ClientDetails client = clientDetailsService.loadClientByClientId(authentication.getOAuth2Request().getClientId());
String revocableHashSignature = getRevocableTokenSignature(client, user);

Expand Down Expand Up @@ -546,9 +553,7 @@ public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication)
OAuth2AccessToken accessToken =
createAccessToken(
userId,
origin,
username,
userEmail,
user,
userAuthenticationTime,
validity != null ? validity.intValue() : accessTokenValiditySeconds,
clientScopes,
Expand Down
Expand Up @@ -33,6 +33,7 @@
import org.cloudfoundry.identity.uaa.user.InMemoryUaaUserDatabase;
import org.cloudfoundry.identity.uaa.user.UaaAuthority;
import org.cloudfoundry.identity.uaa.user.UaaUser;
import org.cloudfoundry.identity.uaa.user.UaaUserPrototype;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
Expand Down Expand Up @@ -73,10 +74,13 @@
import java.util.Set;

import static org.cloudfoundry.identity.uaa.user.UaaAuthority.USER_AUTHORITIES;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;

Expand Down Expand Up @@ -106,6 +110,7 @@ public class UaaTokenServicesTests {
public static final String SCIM = "scim";
public static final String OPENID = "openid";
public static final String ROLES = "roles";
public static final String PROFILE = "profile";

private TestApplicationEventPublisher<TokenIssuedEvent> publisher;
private UaaTokenServices tokenServices = new UaaTokenServices();
Expand All @@ -123,21 +128,23 @@ public class UaaTokenServicesTests {
private final String externalId = "externalId";
private UaaUser defaultUser =
new UaaUser(
userId,
username,
PASSWORD,
email,
defaultUserAuthorities ,
null,
null,
new Date(System.currentTimeMillis() - 15000),
new Date(System.currentTimeMillis() - 15000),
Origin.UAA,
externalId,
false,
IdentityZoneHolder.get().getId(),
userId,
new Date(System.currentTimeMillis() - 15000));
new UaaUserPrototype()
.withId(userId)
.withUsername(username)
.withPassword(PASSWORD)
.withEmail(email)
.withAuthorities(defaultUserAuthorities)
.withGivenName("Marissa")
.withFamilyName("Bloggs")
.withPhoneNumber("1234567890")
.withCreated(new Date(System.currentTimeMillis() - 15000))
.withModified(new Date(System.currentTimeMillis() - 15000))
.withOrigin(Origin.UAA)
.withExternalId(externalId)
.withVerified(false)
.withZoneId(IdentityZoneHolder.get().getId())
.withSalt(userId)
.withPasswordLastModified(new Date(System.currentTimeMillis() - 15000)));

// Need to create a user with a modified time slightly in the past because
// the token IAT is in seconds and the token
Expand Down Expand Up @@ -679,6 +686,22 @@ public void create_id_token_without_roles_scope() {
assertFalse(idTokenJwt.getClaims().contains("\"roles\""));
}

@Test
public void create_id_token_with_profile_scope() throws Exception {
Jwt idTokenJwt = getIdToken(Arrays.asList(OPENID, PROFILE));
assertTrue(idTokenJwt.getClaims().contains("\"given_name\":\"" + defaultUser.getGivenName() + "\""));
assertTrue(idTokenJwt.getClaims().contains("\"family_name\":\"" + defaultUser.getFamilyName() + "\""));
assertTrue(idTokenJwt.getClaims().contains("\"phone_number\":\"" + defaultUser.getPhoneNumber() + "\""));
}

@Test
public void create_id_token_without_profile_scope() throws Exception {
Jwt idTokenJwt = getIdToken(Arrays.asList(OPENID));
assertFalse(idTokenJwt.getClaims().contains("\"given_name\":"));
assertFalse(idTokenJwt.getClaims().contains("\"family_name\":"));
assertFalse(idTokenJwt.getClaims().contains("\"phone_number\":"));
}

private Jwt getIdToken(List<String> scopes) {
AuthorizationRequest authorizationRequest = new AuthorizationRequest(CLIENT_ID, scopes);

Expand Down
2 changes: 2 additions & 0 deletions uaa/src/main/resources/uaa.yml
Expand Up @@ -141,6 +141,8 @@ oauth:
- uaa.user
- approvals.me
- oauth.approvals
- profile
- roles

# Allow unverified users to log in. Defaults to true
#allowUnverifiedUsers: false
Expand Down
Expand Up @@ -84,7 +84,7 @@ public class ScimGroupEndpointsIntegrationTests {

private static final List<String> defaultGroups = Arrays.asList("openid", "scim.me", "cloud_controller.read",
"cloud_controller.write", "password.write", "scim.userids", "uaa.user", "approvals.me",
"oauth.approvals", "cloud_controller_service_permissions.read");
"oauth.approvals", "cloud_controller_service_permissions.read", "profile", "roles");


@Rule
Expand Down Expand Up @@ -180,13 +180,7 @@ private ScimGroup updateGroup(String id, String name, ScimGroupMember... members

private void validateUserGroups(String id, String... groups) {
List<String> groupNames = groups != null ? Arrays.asList(groups) : Collections.<String> emptyList();
assertEquals(groupNames.size() + defaultGroups.size(), getUser(id).getGroups().size()); // there
// are
// 8
// default
// user
// groups
// configured
assertEquals(groupNames.size() + defaultGroups.size(), getUser(id).getGroups().size());
for (ScimUser.Group g : getUser(id).getGroups()) {
assertTrue(defaultGroups.contains(g.getDisplay()) || groupNames.contains(g.getDisplay()));
}
Expand Down
Expand Up @@ -63,7 +63,7 @@ public class ScimUserEndpointsIntegrationTests {

private final String usersEndpoint = "/Users";

private static final int NUM_DEFAULT_GROUPS_ON_STARTUP = 10;
private static final int NUM_DEFAULT_GROUPS_ON_STARTUP = 12;

@Rule
public ServerRunning serverRunning = ServerRunning.isRunning();
Expand Down
Expand Up @@ -845,6 +845,8 @@ public void testLdapScopesFromChainedAuth() throws Exception {
"scim.me",
"cloud_controller_service_permissions.read",
"openid",
"profile",
"roles",
"oauth.approvals",
"uaa.user",
"cloud_controller.read"
Expand Down
Expand Up @@ -34,7 +34,7 @@ public void setUp() throws Exception {

@Test
public void testDefaultAuthorities() throws Exception {
Assert.assertEquals(10, defaultAuthorities.size());
Assert.assertEquals(12, defaultAuthorities.size());
String[] expected = new String[] {
"openid",
"scim.me",
Expand All @@ -45,7 +45,9 @@ public void testDefaultAuthorities() throws Exception {
"scim.userids",
"uaa.user",
"approvals.me",
"oauth.approvals"
"oauth.approvals",
"profile",
"roles"
};
for (String s : expected) {
Assert.assertTrue("Expecting authority to be present:"+s,defaultAuthorities.contains(s));
Expand Down
Expand Up @@ -1251,7 +1251,9 @@ public void testWildcardPasswordGrant() throws Exception {
"scope.two",
"scope.three"));

set1.remove("openid");//not matched here
set1.remove("openid");
set1.remove("profile");
set1.remove("roles");
validatePasswordGrantToken(
clientId,
userId,
Expand Down

0 comments on commit a501a40

Please sign in to comment.