/*
* *****************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2015] 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.saml;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication;
import org.cloudfoundry.identity.uaa.authentication.manager.AuthEvent;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
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.SamlIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory;
import org.cloudfoundry.identity.uaa.scim.ScimGroup;
import org.cloudfoundry.identity.uaa.scim.ScimGroupProvisioning;
import org.cloudfoundry.identity.uaa.scim.ScimUser;
import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning;
import org.cloudfoundry.identity.uaa.scim.bootstrap.ScimUserBootstrap;
import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupExternalMembershipManager;
import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupMembershipManager;
import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupProvisioning;
import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning;
import org.cloudfoundry.identity.uaa.test.JdbcTestBase;
import org.cloudfoundry.identity.uaa.user.JdbcUaaUserDatabase;
import org.cloudfoundry.identity.uaa.user.UaaAuthority;
import org.cloudfoundry.identity.uaa.user.UaaUser;
import org.cloudfoundry.identity.uaa.user.UserInfo;
import org.cloudfoundry.identity.uaa.util.TimeService;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Test;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.Attribute;
import org.opensaml.saml2.core.AuthnContext;
import org.opensaml.saml2.core.AuthnContextClassRef;
import org.opensaml.saml2.core.AuthnStatement;
import org.opensaml.saml2.core.NameID;
import org.opensaml.ws.wsaddressing.impl.AttributedURIImpl;
import org.opensaml.ws.wssecurity.impl.AttributedStringImpl;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.schema.XSBooleanValue;
import org.opensaml.xml.schema.impl.XSAnyImpl;
import org.opensaml.xml.schema.impl.XSBase64BinaryImpl;
import org.opensaml.xml.schema.impl.XSBooleanImpl;
import org.opensaml.xml.schema.impl.XSDateTimeImpl;
import org.opensaml.xml.schema.impl.XSIntegerImpl;
import org.opensaml.xml.schema.impl.XSQNameImpl;
import org.opensaml.xml.schema.impl.XSURIImpl;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.saml.SAMLAuthenticationToken;
import org.springframework.security.saml.SAMLConstants;
import org.springframework.security.saml.SAMLCredential;
import org.springframework.security.saml.context.SAMLMessageContext;
import org.springframework.security.saml.log.SAMLLogger;
import org.springframework.security.saml.metadata.ExtendedMetadata;
import org.springframework.security.saml.websso.WebSSOProfileConsumer;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.xml.namespace.QName;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.GROUP_ATTRIBUTE_NAME;
import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.not;
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.junit.Assert.fail;
import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class LoginSamlAuthenticationProviderTests extends JdbcTestBase {
public static final String SAML_USER = "saml.user";
public static final String SAML_ADMIN = "saml.admin";
public static final String SAML_TEST = "saml.test";
public static final String SAML_NOT_MAPPED = "saml.unmapped";
public static final String UAA_SAML_USER = "uaa.saml.user";
public static final String UAA_SAML_ADMIN = "uaa.saml.admin";
public static final String UAA_SAML_TEST = "uaa.saml.test";
public static final String COST_CENTER = "costCenter";
public static final String DENVER_CO = "Denver,CO";
public static final String MANAGER = "manager";
public static final String JOHN_THE_SLOTH = "John the Sloth";
public static final String KARI_THE_ANT_EATER = "Kari the Ant Eater";
IdentityProviderProvisioning providerProvisioning;
ApplicationEventPublisher publisher;
JdbcUaaUserDatabase userDatabase;
LoginSamlAuthenticationProvider authprovider;
WebSSOProfileConsumer consumer;
SAMLCredential credential;
SAMLLogger samlLogger = mock(SAMLLogger.class);
SamlIdentityProviderDefinition providerDefinition;
private IdentityProvider provider;
private ScimUserProvisioning userProvisioning;
private JdbcScimGroupExternalMembershipManager externalManager;
private ScimGroup uaaSamlUser;
private ScimGroup uaaSamlAdmin;
private ScimGroup uaaSamlTest;
private TimeService timeService;
public List<Attribute> getAttributes(Map<String,Object> values) {
List<Attribute> result = new LinkedList<>();
for (Map.Entry<String,Object> entry : values.entrySet()) {
result.addAll(getAttributes(entry.getKey(), entry.getValue()));
}
return result;
}
public List<Attribute> getAttributes(final String name, Object value) {
Attribute attribute = mock(Attribute.class);
when(attribute.getName()).thenReturn(name);
when(attribute.getFriendlyName()).thenReturn(name);
List<XMLObject> xmlObjects = new LinkedList<>();
if ("XSURI".equals(name)) {
XSURIImpl impl = new AttributedURIImpl("", "", "");
impl.setValue((String)value);
xmlObjects.add(impl);
} else if ("XSAny".equals(name)) {
XSAnyImpl impl = new XSAnyImpl("","","") {};
impl.setTextContent((String)value);
xmlObjects.add(impl);
} else if ("XSQName".equals(name)) {
XSQNameImpl impl = new XSQNameImpl("","","") {};
impl.setValue(new QName("", (String)value));
xmlObjects.add(impl);
} else if ("XSInteger".equals(name)) {
XSIntegerImpl impl = new XSIntegerImpl("","",""){};
impl.setValue((Integer)value);
xmlObjects.add(impl);
} else if ("XSBoolean".equals(name)) {
XSBooleanImpl impl = new XSBooleanImpl("","",""){};
impl.setValue(new XSBooleanValue((Boolean)value, false));
xmlObjects.add(impl);
} else if ("XSDateTime".equals(name)) {
XSDateTimeImpl impl = new XSDateTimeImpl("","",""){};
impl.setValue((DateTime)value);
xmlObjects.add(impl);
} else if ("XSBase64Binary".equals(name)) {
XSBase64BinaryImpl impl = new XSBase64BinaryImpl("","",""){};
impl.setValue((String)value);
xmlObjects.add(impl);
} else if (value instanceof List) {
for (String s : (List<String>) value) {
if (SAML_USER.equals(s)) {
XSAnyImpl impl = new XSAnyImpl("","","") {};
impl.setTextContent(s);
xmlObjects.add(impl);
} else {
AttributedStringImpl impl = new AttributedStringImpl("", "", "");
impl.setValue(s);
xmlObjects.add(impl);
}
}
} else {
AttributedStringImpl impl = new AttributedStringImpl("", "", "");
impl.setValue((String)value);
xmlObjects.add(impl);
}
when(attribute.getAttributeValues()).thenReturn(xmlObjects);
return Arrays.asList(attribute);
}
@Before
public void configureProvider() throws Exception {
providerDefinition = new SamlIdentityProviderDefinition();
userProvisioning = new JdbcScimUserProvisioning(jdbcTemplate, new JdbcPagingListFactory(jdbcTemplate, limitSqlAdapter));
ScimGroupProvisioning groupProvisioning = new JdbcScimGroupProvisioning(jdbcTemplate, new JdbcPagingListFactory(jdbcTemplate, limitSqlAdapter));
uaaSamlUser = groupProvisioning.create(new ScimGroup(null,UAA_SAML_USER, IdentityZone.getUaa().getId()));
uaaSamlAdmin = groupProvisioning.create(new ScimGroup(null,UAA_SAML_ADMIN, IdentityZone.getUaa().getId()));
uaaSamlTest = groupProvisioning.create(new ScimGroup(null,UAA_SAML_TEST, IdentityZone.getUaa().getId()));
JdbcScimGroupMembershipManager membershipManager = new JdbcScimGroupMembershipManager(jdbcTemplate, new JdbcPagingListFactory(jdbcTemplate, limitSqlAdapter));
membershipManager.setScimGroupProvisioning(groupProvisioning);
membershipManager.setScimUserProvisioning(userProvisioning);
ScimUserBootstrap bootstrap = new ScimUserBootstrap(userProvisioning, groupProvisioning, membershipManager, Collections.EMPTY_LIST);
externalManager = new JdbcScimGroupExternalMembershipManager(jdbcTemplate, new JdbcPagingListFactory(jdbcTemplate, limitSqlAdapter));
externalManager.setScimGroupProvisioning(groupProvisioning);
externalManager.mapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML);
externalManager.mapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML);
externalManager.mapExternalGroup(uaaSamlTest.getId(), SAML_TEST, OriginKeys.SAML);
consumer = mock(WebSSOProfileConsumer.class);
credential = getUserCredential("marissa-saml", "Marissa", "Bloggs", "marissa.bloggs@test.com", "1234567890");
Map<String, Object> attributes = new HashMap<>();
attributes.put("firstName", "Marissa");
attributes.put("lastName", "Bloggs");
attributes.put("emailAddress", "marissa.bloggs@test.com");
attributes.put("phone", "1234567890");
attributes.put("groups", Arrays.asList(SAML_USER,SAML_ADMIN,SAML_NOT_MAPPED));
attributes.put("2ndgroups", Arrays.asList(SAML_TEST));
when(consumer.processAuthenticationResponse(anyObject())).thenReturn(credential);
timeService = mock(TimeService.class);
userDatabase = new JdbcUaaUserDatabase(jdbcTemplate, timeService);
userDatabase.setDefaultAuthorities(new HashSet<>(Arrays.asList(UaaAuthority.UAA_USER.getAuthority())));
providerProvisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate);
publisher = new CreateUserPublisher(bootstrap);
authprovider = new LoginSamlAuthenticationProvider();
authprovider.setUserDatabase(userDatabase);
authprovider.setIdentityProviderProvisioning(providerProvisioning);
authprovider.setApplicationEventPublisher(publisher);
authprovider.setConsumer(consumer);
authprovider.setSamlLogger(samlLogger);
authprovider.setExternalMembershipManager(externalManager);
provider = new IdentityProvider();
provider.setIdentityZoneId(IdentityZone.getUaa().getId());
provider.setOriginKey(OriginKeys.SAML);
provider.setName("saml-test");
provider.setActive(true);
provider.setType(OriginKeys.SAML);
providerDefinition.setMetaDataLocation(String.format(IDP_META_DATA, OriginKeys.SAML));
providerDefinition.setIdpEntityAlias(OriginKeys.SAML);
provider.setConfig(providerDefinition);
provider = providerProvisioning.create(provider);
}
private SAMLCredential getUserCredential(String username, String firstName, String lastName, String emailAddress, String phoneNumber) {
NameID usernameID = mock(NameID.class);
when(usernameID.getValue()).thenReturn(username);
Map<String, Object> attributes = new HashMap<>();
attributes.put("firstName", firstName);
attributes.put("lastName", lastName);
attributes.put("emailAddress", emailAddress);
attributes.put("phone", phoneNumber);
attributes.put("groups", Arrays.asList(SAML_USER, SAML_ADMIN, SAML_NOT_MAPPED));
attributes.put("2ndgroups", Arrays.asList(SAML_TEST));
attributes.put(COST_CENTER, Arrays.asList(DENVER_CO));
attributes.put(MANAGER, Arrays.asList(JOHN_THE_SLOTH, KARI_THE_ANT_EATER));
//test different types
attributes.put("XSURI", "http://localhost:8080/someuri");
attributes.put("XSAny", "XSAnyValue");
attributes.put("XSQName", "XSQNameValue");
attributes.put("XSInteger", new Integer(3));
attributes.put("XSBoolean", Boolean.TRUE);
attributes.put("XSDateTime", new DateTime(0));
attributes.put("XSBase64Binary", "00001111");
AuthnContextClassRef contextClassRef = mock(AuthnContextClassRef.class);
when(contextClassRef.getAuthnContextClassRef()).thenReturn(AuthnContext.PASSWORD_AUTHN_CTX);
AuthnContext authenticationContext = mock(AuthnContext.class);
when(authenticationContext.getAuthnContextClassRef()).thenReturn(contextClassRef);
AuthnStatement statement = mock(AuthnStatement.class);
when(statement.getAuthnContext()).thenReturn(authenticationContext);
Assertion authenticationAssertion = mock(Assertion.class);
when(authenticationAssertion.getAuthnStatements()).thenReturn(Arrays.asList(statement));
return new SAMLCredential(
usernameID,
authenticationAssertion,
"remoteEntityID",
getAttributes(attributes),
"localEntityID");
}
@Test
public void testAuthenticateSimple() {
authprovider.authenticate(mockSamlAuthentication(OriginKeys.SAML));
}
@Test
public void saml_authentication_contains_acr() {
Authentication authentication = authprovider.authenticate(mockSamlAuthentication(OriginKeys.SAML));
assertNotNull("Authentication cannot be null", authentication);
assertTrue("Authentication should be of type:"+UaaAuthentication.class.getName(), authentication instanceof UaaAuthentication);
UaaAuthentication uaaAuthentication = (UaaAuthentication)authentication;
assertThat(uaaAuthentication.getAuthContextClassRef(),containsInAnyOrder(AuthnContext.PASSWORD_AUTHN_CTX));
}
@Test
public void test_multiple_group_attributes() throws Exception {
providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, Arrays.asList("2ndgroups", "groups"));
provider.setConfig(providerDefinition);
providerProvisioning.update(provider);
UaaAuthentication authentication = getAuthentication();
assertEquals("Four authorities should have been granted!", 4, authentication.getAuthorities().size());
assertThat(authentication.getAuthorities(),
containsInAnyOrder(
new SimpleGrantedAuthority(UAA_SAML_ADMIN),
new SimpleGrantedAuthority(UAA_SAML_USER),
new SimpleGrantedAuthority(UAA_SAML_TEST),
new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority())
)
);
}
@Test
public void authenticationContainsAmr() throws Exception {
UaaAuthentication authentication = getAuthentication();
assertThat(authentication.getAuthenticationMethods(), containsInAnyOrder("ext"));
}
@Test
public void test_external_groups_as_scopes() throws Exception {
providerDefinition.setGroupMappingMode(SamlIdentityProviderDefinition.ExternalGroupMappingMode.AS_SCOPES);
providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, Arrays.asList("2ndgroups", "groups"));
provider.setConfig(providerDefinition);
providerProvisioning.update(provider);
UaaAuthentication authentication = getAuthentication();
assertThat(authentication.getAuthorities(),
containsInAnyOrder(
new SimpleGrantedAuthority(SAML_ADMIN),
new SimpleGrantedAuthority(SAML_USER),
new SimpleGrantedAuthority(SAML_TEST),
new SimpleGrantedAuthority(SAML_NOT_MAPPED),
new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority())
)
);
}
@Test
public void test_group_mapping() throws Exception {
providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups");
provider.setConfig(providerDefinition);
providerProvisioning.update(provider);
UaaAuthentication authentication = getAuthentication();
assertEquals("Three authorities should have been granted!", 3, authentication.getAuthorities().size());
assertThat(authentication.getAuthorities(),
containsInAnyOrder(
new SimpleGrantedAuthority(UAA_SAML_ADMIN),
new SimpleGrantedAuthority(UAA_SAML_USER),
new SimpleGrantedAuthority(UaaAuthority.UAA_USER.getAuthority())
)
);
}
@Test
public void test_non_string_attributes() throws Exception {
providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+"XSURI", "XSURI");
providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+"XSAny", "XSAny");
providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+"XSQName", "XSQName");
providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+"XSInteger", "XSInteger");
providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+"XSBoolean", "XSBoolean");
providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+"XSDateTime", "XSDateTime");
providerDefinition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+"XSBase64Binary", "XSBase64Binary");
provider.setConfig(providerDefinition);
providerProvisioning.update(provider);
UaaAuthentication authentication = getAuthentication();
assertEquals("http://localhost:8080/someuri", authentication.getUserAttributes().getFirst("XSURI"));
assertEquals("XSAnyValue", authentication.getUserAttributes().getFirst("XSAny"));
assertEquals("XSQNameValue", authentication.getUserAttributes().getFirst("XSQName"));
assertEquals("3", authentication.getUserAttributes().getFirst("XSInteger"));
assertEquals("true", authentication.getUserAttributes().getFirst("XSBoolean"));
assertEquals(new DateTime(0).toString(), authentication.getUserAttributes().getFirst("XSDateTime"));
assertEquals("00001111", authentication.getUserAttributes().getFirst("XSBase64Binary"));
}
@Test
public void externalGroup_NotMapped_ToScope() throws Exception {
try {
externalManager.unmapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML);
externalManager.unmapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML);
providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups");
provider.setConfig(providerDefinition);
providerProvisioning.update(provider);
UaaAuthentication authentication = getAuthentication();
assertEquals("Three authorities should have been granted!", 1, authentication.getAuthorities().size());
assertThat(authentication.getAuthorities(),
not(containsInAnyOrder(
new SimpleGrantedAuthority(UAA_SAML_ADMIN),
new SimpleGrantedAuthority(UAA_SAML_USER)
))
);
} finally {
externalManager.mapExternalGroup(uaaSamlUser.getId(), SAML_USER, OriginKeys.SAML);
externalManager.mapExternalGroup(uaaSamlAdmin.getId(), SAML_ADMIN, OriginKeys.SAML);
}
}
@Test
public void test_group_attribute_not_set() throws Exception {
UaaAuthentication uaaAuthentication = getAuthentication();
assertEquals("Only uaa.user should have been granted", 1, uaaAuthentication.getAuthorities().size());
assertEquals(UaaAuthority.UAA_USER.getAuthority(), uaaAuthentication.getAuthorities().iterator().next().getAuthority());
}
@Test
public void dontAdd_external_groups_to_authentication_without_whitelist() throws Exception {
providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups");
provider.setConfig(providerDefinition);
providerProvisioning.update(provider);
UaaAuthentication authentication = getAuthentication();
assertEquals(Collections.EMPTY_SET, authentication.getExternalGroups());
}
@Test
public void add_external_groups_to_authentication_with_whitelist() throws Exception {
providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups");
providerDefinition.addWhiteListedGroup(SAML_ADMIN);
provider.setConfig(providerDefinition);
providerProvisioning.update(provider);
UaaAuthentication authentication = getAuthentication();
assertEquals(Collections.singleton(SAML_ADMIN), authentication.getExternalGroups());
}
@Test
public void add_external_groups_to_authentication_with_wildcard_whitelist() throws Exception {
providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups");
providerDefinition.addWhiteListedGroup("saml*");
provider.setConfig(providerDefinition);
providerProvisioning.update(provider);
UaaAuthentication authentication = getAuthentication();
assertThat(authentication.getExternalGroups(), containsInAnyOrder(SAML_USER, SAML_ADMIN, SAML_NOT_MAPPED));
}
@Test
public void update_invitedUser_whose_username_is_notEmail() throws Exception {
ScimUser scimUser = getInvitedUser();
SAMLCredential credential = getUserCredential("marissa-invited", "Marissa-invited", null, "marissa.invited@test.org", null);
when(consumer.processAuthenticationResponse(anyObject())).thenReturn(credential);
getAuthentication();
UaaUser user = userDatabase.retrieveUserById(scimUser.getId());
assertTrue(user.isVerified());
assertEquals("marissa-invited", user.getUsername());
assertEquals("marissa.invited@test.org", user.getEmail());
RequestContextHolder.resetRequestAttributes();
}
@Test
public void invitedUser_authentication_whenAuthenticatedEmailDoesNotMatchInvitedEmail() throws Exception {
Map<String,Object> attributeMappings = new HashMap<>();
attributeMappings.put("email", "emailAddress");
providerDefinition.setAttributeMappings(attributeMappings);
provider.setConfig(providerDefinition);
providerProvisioning.update(provider);
ScimUser scimUser = getInvitedUser();
SAMLCredential credential = getUserCredential("marissa-invited", "Marissa-invited", null, "different@test.org", null);
when(consumer.processAuthenticationResponse(anyObject())).thenReturn(credential);
try {
getAuthentication();
fail();
} catch (BadCredentialsException e) {
UaaUser user = userDatabase.retrieveUserById(scimUser.getId());
assertFalse(user.isVerified());
}
RequestContextHolder.resetRequestAttributes();
}
private ScimUser getInvitedUser() {
ScimUser invitedUser = new ScimUser(null, "marissa.invited@test.org", "Marissa", "Bloggs");
invitedUser.setPassword("a");
invitedUser.setVerified(false);
invitedUser.setPrimaryEmail("marissa.invited@test.org");
ScimUser scimUser = userProvisioning.create(invitedUser);
RequestAttributes attributes = new ServletRequestAttributes(new MockHttpServletRequest());
attributes.setAttribute("IS_INVITE_ACCEPTANCE", true, RequestAttributes.SCOPE_SESSION);
attributes.setAttribute("user_id", scimUser.getId(), RequestAttributes.SCOPE_SESSION);
RequestContextHolder.setRequestAttributes(attributes);
return scimUser;
}
@Test
public void update_existingUser_if_attributes_different() throws Exception {
getAuthentication();
Map<String,Object> attributeMappings = new HashMap<>();
attributeMappings.put("given_name", "firstName");
attributeMappings.put("email", "emailAddress");
providerDefinition.setAttributeMappings(attributeMappings);
provider.setConfig(providerDefinition);
providerProvisioning.update(provider);
SAMLCredential credential = getUserCredential("marissa-saml", "Marissa-changed", null, "marissa.bloggs@change.org", null);
when(consumer.processAuthenticationResponse(anyObject())).thenReturn(credential);
getAuthentication();
UaaUser user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML);
assertEquals("Marissa-changed", user.getGivenName());
assertEquals("marissa.bloggs@change.org", user.getEmail());
}
@Test
public void dont_update_existingUser_if_attributes_areTheSame() throws Exception {
getAuthentication();
UaaUser user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML);
getAuthentication();
UaaUser existingUser = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML);
assertEquals(existingUser.getModified(), user.getModified());
}
@Test
public void shadowAccount_createdWith_MappedUserAttributes() throws Exception {
Map<String,Object> attributeMappings = new HashMap<>();
attributeMappings.put("given_name", "firstName");
attributeMappings.put("family_name", "lastName");
attributeMappings.put("email", "emailAddress");
attributeMappings.put("phone_number", "phone");
providerDefinition.setAttributeMappings(attributeMappings);
provider.setConfig(providerDefinition);
providerProvisioning.update(provider);
getAuthentication();
UaaUser user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML);
assertEquals("Marissa", user.getGivenName());
assertEquals("Bloggs", user.getFamilyName());
assertEquals("marissa.bloggs@test.com", user.getEmail());
assertEquals("1234567890", user.getPhoneNumber());
}
@Test
public void custom_user_attributes_stored_if_configured() throws Exception {
Map<String,Object> attributeMappings = new HashMap<>();
attributeMappings.put("given_name", "firstName");
attributeMappings.put("family_name", "lastName");
attributeMappings.put("email", "emailAddress");
attributeMappings.put("phone_number", "phone");
attributeMappings.put(USER_ATTRIBUTE_PREFIX+"secondary_email","emailAddress");
providerDefinition.setAttributeMappings(attributeMappings);
provider.setConfig(providerDefinition);
provider = providerProvisioning.update(provider);
UaaAuthentication authentication = getAuthentication();
UaaUser user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML);
assertEquals("Marissa", user.getGivenName());
assertEquals("Bloggs", user.getFamilyName());
assertEquals("marissa.bloggs@test.com", user.getEmail());
assertEquals("1234567890", user.getPhoneNumber());
assertEquals("marissa.bloggs@test.com", authentication.getUserAttributes().getFirst("secondary_email"));
UserInfo userInfo = userDatabase.getUserInfo(user.getId());
assertNull(userInfo);
providerDefinition.addAttributeMapping(GROUP_ATTRIBUTE_NAME, "groups");
providerDefinition.addWhiteListedGroup(SAML_ADMIN);
providerDefinition.setStoreCustomAttributes(true);
provider.setConfig(providerDefinition);
provider = providerProvisioning.update(provider);
authentication = getAuthentication();
assertEquals("marissa.bloggs@test.com", authentication.getUserAttributes().getFirst("secondary_email"));
userInfo = userDatabase.getUserInfo(user.getId());
assertNotNull(userInfo);
assertEquals("marissa.bloggs@test.com", userInfo.getUserAttributes().getFirst("secondary_email"));
assertNotNull(userInfo.getRoles());
assertEquals(1, userInfo.getRoles().size());
assertEquals(SAML_ADMIN, userInfo.getRoles().get(0));
}
@Test
public void shadowAccountNotCreated_givenShadowAccountCreationDisabled() throws Exception {
Map<String,Object> attributeMappings = new HashMap<>();
attributeMappings.put("given_name", "firstName");
attributeMappings.put("family_name", "lastName");
attributeMappings.put("email", "emailAddress");
attributeMappings.put("phone_number", "phone");
providerDefinition.setAttributeMappings(attributeMappings);
providerDefinition.setAddShadowUserOnLogin(false);
provider.setConfig(providerDefinition);
providerProvisioning.update(provider);
try {
getAuthentication();
fail("Expected authentication to throw LoginSAMLException");
} catch (LoginSAMLException ex) {
}
try {
userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML);
fail("Expected user not to exist in database");
} catch(UsernameNotFoundException ex) {
}
}
@Test
public void should_NotCreateShadowAccount_AndInstead_UpdateExistingUserUsername_if_userWithEmailExists() throws Exception {
Map<String,Object> attributeMappings = new HashMap<>();
attributeMappings.put("email", "emailAddress");
providerDefinition.setAttributeMappings(attributeMappings);
provider.setConfig(providerDefinition);
providerProvisioning.update(provider);
ScimUser createdUser = createSamlUser("marissa.bloggs@test.com", "marissa.bloggs@test.com", "Marissa", "Bloggs");
getAuthentication();
UaaUser uaaUser = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML);
assertEquals(createdUser.getId(), uaaUser.getId());
assertEquals("marissa-saml", uaaUser.getUsername());
}
@Test(expected = IncorrectResultSizeDataAccessException.class)
public void error_when_multipleUsers_with_sameEmail() throws Exception {
Map<String,Object> attributeMappings = new HashMap<>();
attributeMappings.put("email", "emailAddress");
providerDefinition.setAttributeMappings(attributeMappings);
provider.setConfig(providerDefinition);
providerProvisioning.update(provider);
createSamlUser("marissa.bloggs@test.com", "marissa.bloggs@test.com", "Marissa", "Bloggs");
createSamlUser("marissa.bloggs", "marissa.bloggs@test.com", "Marissa", "Bloggs");
getAuthentication();
}
private ScimUser createSamlUser(String username, String email, String givenName, String familyName) {
ScimUser user = new ScimUser("", username, givenName, familyName);
user.setPrimaryEmail(email);
user.setOrigin(OriginKeys.SAML);
return userProvisioning.createUser(user, "");
}
@Test
public void shadowUser_GetsCreatedWithDefaultValues_IfAttributeNotMapped() throws Exception {
Map<String,Object> attributeMappings = new HashMap<>();
attributeMappings.put("surname", "lastName");
attributeMappings.put("email", "emailAddress");
providerDefinition.setAttributeMappings(attributeMappings);
provider.setConfig(providerDefinition);
providerProvisioning.update(provider);
UaaAuthentication authentication = getAuthentication();
UaaUser user = userDatabase.retrieveUserByName("marissa-saml", OriginKeys.SAML);
assertEquals("marissa.bloggs", user.getGivenName());
assertEquals("test.com", user.getFamilyName());
assertEquals("marissa.bloggs@test.com", user.getEmail());
assertEquals("No custom attributes have been mapped", 0, authentication.getUserAttributes().size());
}
@Test
public void user_authentication_contains_custom_attributes() throws Exception {
String COST_CENTERS = COST_CENTER+"s";
String MANAGERS = MANAGER+"s";
Map<String,Object> attributeMappings = new HashMap<>();
attributeMappings.put(USER_ATTRIBUTE_PREFIX+COST_CENTERS, COST_CENTER);
attributeMappings.put(USER_ATTRIBUTE_PREFIX+MANAGERS, MANAGER);
providerDefinition.setAttributeMappings(attributeMappings);
provider.setConfig(providerDefinition);
providerProvisioning.update(provider);
UaaAuthentication authentication = getAuthentication();
assertEquals("Expected two user attributes", 2, authentication.getUserAttributes().size());
assertNotNull("Expected cost center attribute", authentication.getUserAttributes().get(COST_CENTERS));
assertEquals(DENVER_CO, authentication.getUserAttributes().getFirst(COST_CENTERS));
assertNotNull("Expected manager attribute", authentication.getUserAttributes().get(MANAGERS));
assertEquals("Expected 2 manager attribute values", 2, authentication.getUserAttributes().get(MANAGERS).size());
assertThat(authentication.getUserAttributes().get(MANAGERS), containsInAnyOrder(JOHN_THE_SLOTH, KARI_THE_ANT_EATER));
}
protected UaaAuthentication getAuthentication() {
SAMLAuthenticationToken authentication1 = mockSamlAuthentication(OriginKeys.SAML);
Authentication authentication = authprovider.authenticate(authentication1);
assertNotNull("Authentication should exist", authentication);
assertTrue("Authentication should be UaaAuthentication", authentication instanceof UaaAuthentication);
return (UaaAuthentication)authentication;
}
protected SAMLAuthenticationToken mockSamlAuthentication(String originKey) {
ExtendedMetadata metadata = mock(ExtendedMetadata.class);
when(metadata.getAlias()).thenReturn(originKey);
SAMLMessageContext contxt = mock(SAMLMessageContext.class);
when(contxt.getPeerExtendedMetadata()).thenReturn(metadata);
when(contxt.getCommunicationProfileId()).thenReturn(SAMLConstants.SAML2_WEBSSO_PROFILE_URI);
return new SAMLAuthenticationToken(contxt);
}
public static class CreateUserPublisher implements ApplicationEventPublisher {
final ScimUserBootstrap bootstrap;
public CreateUserPublisher(ScimUserBootstrap bootstrap) {
this.bootstrap = bootstrap;
}
@Override
public void publishEvent(ApplicationEvent event) {
if (event instanceof AuthEvent) {
bootstrap.onApplicationEvent((AuthEvent)event);
}
}
@Override
public void publishEvent(Object event) {
throw new UnsupportedOperationException("not implemented");
}
}
public static final String IDP_META_DATA =
"<?xml version=\"1.0\"?>\n" +
"<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" entityID=\"%s\" ID=\"pfx06ad4153-c17c-d286-194c-dec30bb92796\"><ds:Signature>\n" +
" <ds:SignedInfo><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n" +
" <ds:SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#rsa-sha1\"/>\n" +
" <ds:Reference URI=\"#pfx06ad4153-c17c-d286-194c-dec30bb92796\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/><ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"/><ds:DigestValue>begl1WVCsXSn7iHixtWPP8d/X+k=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>BmbKqA3A0oSLcn5jImz/l5WbpVXj+8JIpT/ENWjOjSd/gcAsZm1QvYg+RxYPBk+iV2bBxD+/yAE/w0wibsHrl0u9eDhoMRUJBUSmeyuN1lYzBuoVa08PdAGtb5cGm4DMQT5Rzakb1P0hhEPPEDDHgTTxop89LUu6xx97t2Q03Khy8mXEmBmNt2NlFxJPNt0FwHqLKOHRKBOE/+BpswlBocjOQKFsI9tG3TyjFC68mM2jo0fpUQCgj5ZfhzolvS7z7c6V201d9Tqig0/mMFFJLTN8WuZPavw22AJlMjsDY9my+4R9HKhK5U53DhcTeECs9fb4gd7p5BJy4vVp7tqqOg==</ds:SignatureValue>\n" +
"<ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature>\n" +
" <md:IDPSSODescriptor protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n" +
" <md:KeyDescriptor use=\"signing\">\n" +
" <ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
" <ds:X509Data>\n" +
" <ds:X509Certificate>MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk</ds:X509Certificate>\n" +
" </ds:X509Data>\n" +
" </ds:KeyInfo>\n" +
" </md:KeyDescriptor>\n" +
" <md:KeyDescriptor use=\"encryption\">\n" +
" <ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n" +
" <ds:X509Data>\n" +
" <ds:X509Certificate>MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk</ds:X509Certificate>\n" +
" </ds:X509Data>\n" +
" </ds:KeyInfo>\n" +
" </md:KeyDescriptor>\n" +
" <md:SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://simplesamlphp.cfapps.io/saml2/idp/SingleLogoutService.php\"/>\n" +
" <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>\n" +
" <md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://simplesamlphp.cfapps.io/saml2/idp/SSOService.php\"/>\n" +
" </md:IDPSSODescriptor>\n" +
" <md:ContactPerson contactType=\"technical\">\n" +
" <md:GivenName>Filip</md:GivenName>\n" +
" <md:SurName>Hanik</md:SurName>\n" +
" <md:EmailAddress>fhanik@pivotal.io</md:EmailAddress>\n" +
" </md:ContactPerson>\n" +
"</md:EntityDescriptor>";
}