Skip to content

Commit

Permalink
Implement SAML attribute mapping that is stored
Browse files Browse the repository at this point in the history
  • Loading branch information
fhanik committed Dec 16, 2016
1 parent a676fb0 commit 51f538f
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 37 deletions.
Expand Up @@ -3,13 +3,14 @@
import com.fasterxml.jackson.annotation.JsonIgnore;

import java.util.Collections;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;

/*******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved.
Expand Down Expand Up @@ -88,7 +89,7 @@ public boolean equals(Object o) {
ExternalIdentityProviderDefinition that = (ExternalIdentityProviderDefinition) o;

if (addShadowUserOnLogin != that.addShadowUserOnLogin) return false;
if(this.areCustomAttributesStored() != that.areCustomAttributesStored()) return false;
if(this.isStoreCustomAttributes() != that.isStoreCustomAttributes()) return false;
if (getExternalGroupsWhitelist() != null ? !getExternalGroupsWhitelist().equals(that.getExternalGroupsWhitelist()) : that.getExternalGroupsWhitelist() != null)
return false;
return attributeMappings != null ? attributeMappings.equals(that.attributeMappings) : that.attributeMappings == null;
Expand All @@ -103,7 +104,7 @@ public int hashCode() {
return result;
}

public boolean areCustomAttributesStored() {
public boolean isStoreCustomAttributes() {
return storeCustomAttributes;
}

Expand Down
Expand Up @@ -81,6 +81,7 @@ public SamlIdentityProviderDefinition clone() {
def.setGroupMappingMode(getGroupMappingMode());
def.setSocketFactoryClassName(getSocketFactoryClassName());
def.setSkipSslValidation(isSkipSslValidation());
def.setStoreCustomAttributes(isStoreCustomAttributes());
return def;
}

Expand Down
Expand Up @@ -78,7 +78,9 @@ protected UserInfoResponse getResponse(UaaPrincipal principal, boolean addCustom
// TODO: other attributes
if (addCustomAttributes) {
UserInfo info = userDatabase.getUserInfo(user.getId());
response.setAttributeValue(USER_ATTRIBUTES, info);
if (info!=null && info.size()>0) {
response.setAttributeValue(USER_ATTRIBUTES, info);
}
}
return response;
}
Expand Down
Expand Up @@ -167,7 +167,7 @@ protected void populateAuthenticationAttributes(UaaAuthentication authentication
authentication.getAuthenticationMethods().add("ext");
if (authentication.getUserAttributes()!=null && authentication.getUserAttributes().size()>0 && getProviderProvisioning()!=null) {
IdentityProvider<ExternalIdentityProviderDefinition> provider = getProviderProvisioning().retrieveByOrigin(getOrigin(), IdentityZoneHolder.get().getId());
if (provider.getConfig()!=null && provider.getConfig().areCustomAttributesStored()) {
if (provider.getConfig()!=null && provider.getConfig().isStoreCustomAttributes()) {
logger.debug("Storing custom attributes for user_id:"+authentication.getPrincipal().getId());
getUserDatabase().storeUserInfo(authentication.getPrincipal().getId(), new UserInfo(authentication.getUserAttributes()));
}
Expand Down
Expand Up @@ -32,6 +32,7 @@
import org.cloudfoundry.identity.uaa.user.UaaUser;
import org.cloudfoundry.identity.uaa.user.UaaUserDatabase;
import org.cloudfoundry.identity.uaa.user.UaaUserPrototype;
import org.cloudfoundry.identity.uaa.user.UserInfo;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.joda.time.DateTime;
Expand Down Expand Up @@ -161,7 +162,11 @@ public Authentication authenticate(Authentication authentication) throws Authent
MultiValueMap<String, String> userAttributes = retrieveUserAttributes(samlConfig, (SAMLCredential) result.getCredentials());
UaaUser user = createIfMissing(samlPrincipal, addNew, authorities, userAttributes);
UaaPrincipal principal = new UaaPrincipal(user);
return new LoginSamlAuthenticationToken(principal, result).getUaaAuthentication(user.getAuthorities(), filteredExternalGroups, userAttributes);
UaaAuthentication resultUaaAuthentication = new LoginSamlAuthenticationToken(principal, result).getUaaAuthentication(user.getAuthorities(), filteredExternalGroups, userAttributes);
if (samlConfig.isStoreCustomAttributes()) {
userDatabase.storeUserInfo(user.getId(), new UserInfo(resultUaaAuthentication.getUserAttributes()));
}
return resultUaaAuthentication;
}

protected ExpiringUsernameAuthenticationToken getExpiringUsernameAuthenticationToken(Authentication authentication) {
Expand Down
Expand Up @@ -13,6 +13,8 @@
package org.cloudfoundry.identity.uaa.user;


import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.springframework.dao.EmptyResultDataAccessException;
Expand Down Expand Up @@ -43,6 +45,8 @@
*/
public class JdbcUaaUserDatabase implements UaaUserDatabase {

private static Log logger = LogFactory.getLog(JdbcUaaUserDatabase.class);

public static final String USER_FIELDS = "id,username,password,email,givenName,familyName,created,lastModified,authorities,origin,external_id,verified,identity_zone_id,salt,passwd_lastmodified,phoneNumber,legacy_verification_behavior,passwd_change_required ";

public static final String PRE_DEFAULT_USER_BY_USERNAME_QUERY = "select " + USER_FIELDS + "from users where %s = ? and active=? and origin=? and identity_zone_id=?";
Expand Down Expand Up @@ -130,7 +134,12 @@ else if(results.size() == 1) {

@Override
public UserInfo getUserInfo(String id) {
return jdbcTemplate.queryForObject("select user_id, info from user_info where user_id = ?", userInfoMapper, id);
try {
return jdbcTemplate.queryForObject("select user_id, info from user_info where user_id = ?", userInfoMapper, id);
} catch (EmptyResultDataAccessException e) {
logger.debug("No custom attributes stored for user:"+id);
return null;
}
}

@Override
Expand All @@ -139,10 +148,15 @@ public UserInfo storeUserInfo(String id, UserInfo info) {
throw new NullPointerException("id is a required field");
}
final String insertUserInfoSQL = "insert into user_info(user_id, info) values (?,?)";
final String updateUserInfoSQL = "update user_info set info = ? where user_id = ?";
if (info == null) {
info = new UserInfo();
}
jdbcTemplate.update(insertUserInfoSQL, id, JsonUtils.writeValueAsString(info));
String json = JsonUtils.writeValueAsString(info);
int count = jdbcTemplate.update(updateUserInfoSQL, json, id);
if (count == 0) {
jdbcTemplate.update(insertUserInfoSQL, id, json);
}
return getUserInfo(id);
}

Expand Down
Expand Up @@ -57,7 +57,7 @@ public void testEquals() {
public void test_serialize_custom_attributes_field() {
definition.setStoreCustomAttributes(true);
SamlIdentityProviderDefinition def = JsonUtils.readValue(JsonUtils.writeValueAsString(definition), SamlIdentityProviderDefinition.class);
assertTrue(def.areCustomAttributesStored());
assertTrue(def.isStoreCustomAttributes());
}

@Test
Expand Down
Expand Up @@ -150,6 +150,11 @@ public void testStoreUserInfo() {
db.storeUserInfo(id, info);
UserInfo info2 = db.getUserInfo(id);
assertEquals(info, info2);

info.put("new","value");
db.storeUserInfo(id, info);
UserInfo info3 = db.getUserInfo(id);
assertEquals(info, info3);
}

@Test
Expand Down
Expand Up @@ -14,6 +14,7 @@

import com.fasterxml.jackson.core.type.TypeReference;
import org.cloudfoundry.identity.uaa.ServerRunning;
import org.cloudfoundry.identity.uaa.account.UserInfoResponse;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils;
import org.cloudfoundry.identity.uaa.integration.util.ScreenshotOnFail;
Expand Down Expand Up @@ -75,6 +76,7 @@
import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.createSimplePHPSamlIDP;
import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.doesSupportZoneDNS;
import static org.cloudfoundry.identity.uaa.integration.util.IntegrationTestUtils.getZoneAdminToken;
import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.USER_ATTRIBUTES;
import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.EMAIL_ATTRIBUTE_NAME;
import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME;
import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX;
Expand Down Expand Up @@ -797,6 +799,7 @@ public void testSamlLogin_Custom_User_Attributes_In_ID_Token() throws Exception
"secr3T");

SamlIdentityProviderDefinition samlIdentityProviderDefinition = createTestZone1IDP("simplesamlphp");
samlIdentityProviderDefinition.setStoreCustomAttributes(true);
samlIdentityProviderDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+COST_CENTERS, COST_CENTER);
samlIdentityProviderDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+MANAGERS, MANAGER);

Expand Down Expand Up @@ -865,15 +868,24 @@ public void testSamlLogin_Custom_User_Attributes_In_ID_Token() throws Exception
Jwt idTokenClaims = JwtHelper.decode(idToken);
Map<String, Object> claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference<Map<String, Object>>() {});

assertNotNull(claims.get(ClaimConstants.USER_ATTRIBUTES));
Map<String,List<String>> userAttributes = (Map<String, List<String>>) claims.get(ClaimConstants.USER_ATTRIBUTES);
assertNotNull(claims.get(USER_ATTRIBUTES));
Map<String,List<String>> userAttributes = (Map<String, List<String>>) claims.get(USER_ATTRIBUTES);
assertThat(userAttributes.get(COST_CENTERS), containsInAnyOrder(DENVER_CO));
assertThat(userAttributes.get(MANAGERS), containsInAnyOrder(JOHN_THE_SLOTH, KARI_THE_ANT_EATER));

assertNotNull("id_token should contain ACR claim", claims.get(ClaimConstants.ACR));
Map<String,Object> acr = (Map<String, Object>) claims.get(ClaimConstants.ACR);
assertNotNull("acr claim should contain values attribute", acr.get("values"));
assertThat((List<String>) acr.get("values"), containsInAnyOrder(AuthnContext.PASSWORD_AUTHN_CTX));

UserInfoResponse userInfo = IntegrationTestUtils.getUserInfo(zoneUrl, authCodeTokenResponse.get("access_token"));

Map<String,List<String>> userAttributeMap = (Map<String,List<String>>) userInfo.getAttributeValue(USER_ATTRIBUTES);
List<String> costCenterData = userAttributeMap.get(COST_CENTERS);
List<String> managerData = userAttributeMap.get(MANAGERS);
assertThat(costCenterData, containsInAnyOrder(DENVER_CO));
assertThat(managerData, containsInAnyOrder(JOHN_THE_SLOTH, KARI_THE_ANT_EATER));

}

@Test
Expand Down Expand Up @@ -977,7 +989,7 @@ public void testSamlLogin_Email_In_ID_Token_When_UserID_IsNotEmail() throws Exce
Jwt idTokenClaims = JwtHelper.decode(idToken);
Map<String, Object> claims = JsonUtils.readValue(idTokenClaims.getClaims(), new TypeReference<Map<String, Object>>() {});

assertNotNull(claims.get(ClaimConstants.USER_ATTRIBUTES));
assertNotNull(claims.get(USER_ATTRIBUTES));
assertEquals("marissa6", claims.get(ClaimConstants.USER_NAME));
assertEquals("marissa6@test.org", claims.get(ClaimConstants.EMAIL));
}
Expand Down

0 comments on commit 51f538f

Please sign in to comment.