From 74356bba3966d78b05ea9447fecb9ca44222eb9f Mon Sep 17 00:00:00 2001 From: Adrian Hoelzl Date: Fri, 23 Aug 2024 09:03:30 +0200 Subject: [PATCH 01/13] Add ScimUserService as central entrypoint for ScimUser update with alias handling --- .../AliasPropertiesInvalidException.java | 7 ++ .../uaa/scim/services/ScimUserService.java | 71 +++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/alias/AliasPropertiesInvalidException.java create mode 100644 server/src/main/java/org/cloudfoundry/identity/uaa/scim/services/ScimUserService.java diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/alias/AliasPropertiesInvalidException.java b/server/src/main/java/org/cloudfoundry/identity/uaa/alias/AliasPropertiesInvalidException.java new file mode 100644 index 00000000000..8490a960ca3 --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/alias/AliasPropertiesInvalidException.java @@ -0,0 +1,7 @@ +package org.cloudfoundry.identity.uaa.alias; + +public class AliasPropertiesInvalidException extends RuntimeException { + public AliasPropertiesInvalidException() { + super("The fields 'aliasId' and/or 'aliasZid' are invalid."); + } +} diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/services/ScimUserService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/services/ScimUserService.java new file mode 100644 index 00000000000..d0e36fb16ac --- /dev/null +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/services/ScimUserService.java @@ -0,0 +1,71 @@ +package org.cloudfoundry.identity.uaa.scim.services; + +import org.cloudfoundry.identity.uaa.alias.AliasPropertiesInvalidException; +import org.cloudfoundry.identity.uaa.alias.EntityAliasFailedException; +import org.cloudfoundry.identity.uaa.scim.ScimUser; +import org.cloudfoundry.identity.uaa.scim.ScimUserAliasHandler; +import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.dao.OptimisticLockingFailureException; +import org.springframework.stereotype.Component; +import org.springframework.transaction.support.TransactionTemplate; + +@Component +public class ScimUserService { + + private final ScimUserAliasHandler aliasHandler; + private final ScimUserProvisioning scimUserProvisioning; + private final IdentityZoneManager identityZoneManager; + private final TransactionTemplate transactionTemplate; + private final boolean aliasEntitiesEnabled; + + public ScimUserService( + final ScimUserAliasHandler aliasHandler, + final ScimUserProvisioning scimUserProvisioning, + final IdentityZoneManager identityZoneManager, + final TransactionTemplate transactionTemplate, + @Qualifier("aliasEntitiesEnabled") final boolean aliasEntitiesEnabled + ) { + this.aliasHandler = aliasHandler; + this.scimUserProvisioning = scimUserProvisioning; + this.identityZoneManager = identityZoneManager; + this.transactionTemplate = transactionTemplate; + this.aliasEntitiesEnabled = aliasEntitiesEnabled; + } + + public ScimUser updateUser(final String userId, final ScimUser user) + throws AliasPropertiesInvalidException, OptimisticLockingFailureException, EntityAliasFailedException { + final ScimUser existingScimUser = scimUserProvisioning.retrieve( + userId, + identityZoneManager.getCurrentIdentityZoneId() + ); + if (!aliasHandler.aliasPropertiesAreValid(user, existingScimUser)) { + throw new AliasPropertiesInvalidException(); + } + + if (!aliasEntitiesEnabled) { + // update user without alias handling + return scimUserProvisioning.update(userId, user, identityZoneManager.getCurrentIdentityZoneId()); + } + + // update user and create/update alias, if necessary + return updateUserWithAliasHandling(userId, user, existingScimUser); + } + + private ScimUser updateUserWithAliasHandling( + final String userId, + final ScimUser user, + final ScimUser existingUser + ) { + return transactionTemplate.execute(txStatus -> { + final ScimUser updatedOriginalUser = scimUserProvisioning.update( + userId, + user, + identityZoneManager.getCurrentIdentityZoneId() + ); + return aliasHandler.ensureConsistencyOfAliasEntity(updatedOriginalUser, existingUser); + }); + } + +} From a6bb5d30dec5d2999477bef358dd27fe3c3b059e Mon Sep 17 00:00:00 2001 From: Adrian Hoelzl Date: Fri, 23 Aug 2024 09:16:28 +0200 Subject: [PATCH 02/13] Move ScimUser update logic from ScimUserEndpoints to ScimUserService --- .../uaa/scim/ScimUserAliasHandler.java | 2 +- .../uaa/scim/endpoints/ScimUserEndpoints.java | 37 ++++--------------- .../bootstrap/ScimUserBootstrapTests.java | 29 ++++++++++++++- .../ScimUserEndpointsAliasTests.java | 10 +++++ .../endpoints/ScimUserEndpointsTests.java | 14 ++++++- 5 files changed, 58 insertions(+), 34 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserAliasHandler.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserAliasHandler.java index 48b89b50016..e3a3c1c1349 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserAliasHandler.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/ScimUserAliasHandler.java @@ -23,7 +23,7 @@ public class ScimUserAliasHandler extends EntityAliasHandler { private final IdentityProviderProvisioning identityProviderProvisioning; private final IdentityZoneManager identityZoneManager; - protected ScimUserAliasHandler( + public ScimUserAliasHandler( @Qualifier("identityZoneProvisioning") final IdentityZoneProvisioning identityZoneProvisioning, final ScimUserProvisioning scimUserProvisioning, final IdentityProviderProvisioning identityProviderProvisioning, diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java index f3cf830994b..ad2bb41a92b 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpoints.java @@ -3,6 +3,7 @@ import com.jayway.jsonpath.JsonPathException; import org.cloudfoundry.identity.uaa.account.UserAccountStatus; import org.cloudfoundry.identity.uaa.account.event.UserAccountUnlockedEvent; +import org.cloudfoundry.identity.uaa.alias.AliasPropertiesInvalidException; import org.cloudfoundry.identity.uaa.alias.EntityAliasFailedException; import org.cloudfoundry.identity.uaa.approval.Approval; import org.cloudfoundry.identity.uaa.approval.ApprovalStore; @@ -33,6 +34,7 @@ import org.cloudfoundry.identity.uaa.scim.exception.ScimException; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceConflictException; import org.cloudfoundry.identity.uaa.scim.exception.UserAlreadyVerifiedException; +import org.cloudfoundry.identity.uaa.scim.services.ScimUserService; import org.cloudfoundry.identity.uaa.scim.util.ScimUtils; import org.cloudfoundry.identity.uaa.scim.validate.PasswordValidator; import org.cloudfoundry.identity.uaa.security.IsSelfCheck; @@ -142,6 +144,7 @@ public class ScimUserEndpoints implements InitializingBean, ApplicationEventPubl */ private final AtomicInteger scimDeletes; private final Map errorCounts; + private final ScimUserService scimUserService; private final ScimUserAliasHandler aliasHandler; private final TransactionTemplate transactionTemplate; @@ -161,6 +164,7 @@ public ScimUserEndpoints( final ExpiringCodeStore codeStore, final ApprovalStore approvalStore, final ScimGroupMembershipManager membershipManager, + final ScimUserService scimUserService, final ScimUserAliasHandler aliasHandler, final TransactionTemplate transactionTemplate, final @Qualifier("aliasEntitiesEnabled") boolean aliasEntitiesEnabled, @@ -187,6 +191,7 @@ public ScimUserEndpoints( this.messageConverters = new HttpMessageConverter[] { new ExceptionReportHttpMessageConverter() }; + this.scimUserService = scimUserService; this.aliasHandler = aliasHandler; this.transactionTemplate = transactionTemplate; scimUpdates = new AtomicInteger(); @@ -319,23 +324,11 @@ public ScimUser updateUser(@RequestBody ScimUser user, @PathVariable String user user.setZoneId(identityZoneManager.getCurrentIdentityZoneId()); - final ScimUser existingScimUser = scimUserProvisioning.retrieve( - userId, - identityZoneManager.getCurrentIdentityZoneId() - ); - if (!aliasHandler.aliasPropertiesAreValid(user, existingScimUser)) { - throw new ScimException("The fields 'aliasId' and/or 'aliasZid' are invalid.", HttpStatus.BAD_REQUEST); - } - final ScimUser scimUser; try { - if (aliasEntitiesEnabled) { - // update user and create/update alias, if necessary - scimUser = updateUserWithAliasHandling(userId, user, existingScimUser); - } else { - // update user without alias handling - scimUser = scimUserProvisioning.update(userId, user, identityZoneManager.getCurrentIdentityZoneId()); - } + scimUser = scimUserService.updateUser(userId, user); + } catch (final AliasPropertiesInvalidException e) { + throw new ScimException("The fields 'aliasId' and/or 'aliasZid' are invalid.", HttpStatus.BAD_REQUEST); } catch (final OptimisticLockingFailureException e) { throw new ScimResourceConflictException(e.getMessage()); } catch (final EntityAliasFailedException e) { @@ -348,20 +341,6 @@ public ScimUser updateUser(@RequestBody ScimUser user, @PathVariable String user return scimUserWithApprovalsAndGroups; } - private ScimUser updateUserWithAliasHandling(final String userId, final ScimUser user, final ScimUser existingUser) { - return transactionTemplate.execute(txStatus -> { - final ScimUser updatedOriginalUser = scimUserProvisioning.update( - userId, - user, - identityZoneManager.getCurrentIdentityZoneId() - ); - return aliasHandler.ensureConsistencyOfAliasEntity( - updatedOriginalUser, - existingUser - ); - }); - } - @RequestMapping(value = "/Users/{userId}", method = RequestMethod.PATCH) @ResponseBody public ScimUser patchUser(@RequestBody ScimUser patch, @PathVariable String userId, diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java index c2494821483..bbb04317ab8 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java @@ -5,17 +5,21 @@ import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent; import org.cloudfoundry.identity.uaa.authentication.manager.InvitedUserAuthenticatedEvent; import org.cloudfoundry.identity.uaa.constants.OriginKeys; +import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; +import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.resources.jdbc.JdbcPagingListFactory; import org.cloudfoundry.identity.uaa.resources.jdbc.LimitSqlAdapterFactory; import org.cloudfoundry.identity.uaa.scim.ScimGroup; import org.cloudfoundry.identity.uaa.scim.ScimGroupMember; import org.cloudfoundry.identity.uaa.scim.ScimUser; +import org.cloudfoundry.identity.uaa.scim.ScimUserAliasHandler; import org.cloudfoundry.identity.uaa.scim.endpoints.ScimUserEndpoints; import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException; import org.cloudfoundry.identity.uaa.scim.exception.MemberNotFoundException; 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.scim.services.ScimUserService; import org.cloudfoundry.identity.uaa.security.IsSelfCheck; import org.cloudfoundry.identity.uaa.test.TestUtils; import org.cloudfoundry.identity.uaa.user.UaaUser; @@ -24,6 +28,7 @@ import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.cloudfoundry.identity.uaa.zone.JdbcIdentityZoneProvisioning; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManagerImpl; import org.hamcrest.collection.IsArrayContainingInAnyOrder; import org.junit.jupiter.api.AfterEach; @@ -91,18 +96,37 @@ class ScimUserBootstrapTests { @Autowired private PasswordEncoder passwordEncoder; + private ScimUserService scimUserService; @BeforeEach void init() throws SQLException { JdbcPagingListFactory pagingListFactory = new JdbcPagingListFactory(namedJdbcTemplate, LimitSqlAdapterFactory.getLimitSqlAdapter()); - jdbcScimUserProvisioning = spy(new JdbcScimUserProvisioning(namedJdbcTemplate, pagingListFactory, passwordEncoder, new IdentityZoneManagerImpl(), new JdbcIdentityZoneProvisioning(jdbcTemplate))); + final IdentityZoneManager identityZoneManager = new IdentityZoneManagerImpl(); + final JdbcIdentityZoneProvisioning identityZoneProvisioning = new JdbcIdentityZoneProvisioning(jdbcTemplate); + jdbcScimUserProvisioning = spy(new JdbcScimUserProvisioning(namedJdbcTemplate, pagingListFactory, passwordEncoder, + identityZoneManager, identityZoneProvisioning)); DbUtils dbUtils = new DbUtils(); jdbcScimGroupProvisioning = new JdbcScimGroupProvisioning(namedJdbcTemplate, pagingListFactory, dbUtils); jdbcScimGroupMembershipManager = new JdbcScimGroupMembershipManager( jdbcTemplate, new TimeServiceImpl(), jdbcScimUserProvisioning, null, dbUtils); jdbcScimGroupMembershipManager.setScimGroupProvisioning(jdbcScimGroupProvisioning); + final IdentityProviderProvisioning idpProvisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate); + final ScimUserAliasHandler scimUserAliasHandler = new ScimUserAliasHandler( + identityZoneProvisioning, + jdbcScimUserProvisioning, + idpProvisioning, + identityZoneManager, + false + ); + scimUserService = new ScimUserService( + scimUserAliasHandler, + jdbcScimUserProvisioning, + identityZoneManager, + null, // not required since alias is disabled + false + ); scimUserEndpoints = new ScimUserEndpoints( - new IdentityZoneManagerImpl(), + identityZoneManager, new IsSelfCheck(null), jdbcScimUserProvisioning, null, @@ -112,6 +136,7 @@ void init() throws SQLException { null, null, jdbcScimGroupMembershipManager, + scimUserService, null, null, false, diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsAliasTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsAliasTests.java index f77ab48f9f3..f78d2a5b27b 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsAliasTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsAliasTests.java @@ -14,6 +14,7 @@ import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; import org.cloudfoundry.identity.uaa.scim.exception.ScimException; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceConflictException; +import org.cloudfoundry.identity.uaa.scim.services.ScimUserService; import org.cloudfoundry.identity.uaa.scim.validate.PasswordValidator; import org.cloudfoundry.identity.uaa.security.IsSelfCheck; import org.cloudfoundry.identity.uaa.util.AlphanumericRandomValueStringGenerator; @@ -89,6 +90,14 @@ class ScimUserEndpointsAliasTests { @BeforeEach void setUp() { + final ScimUserService scimUserService = new ScimUserService( + scimUserAliasHandler, + scimUserProvisioning, + identityZoneManager, + transactionTemplate, + true // alias entities are enabled + ); + scimUserEndpoints = new ScimUserEndpoints( identityZoneManager, isSelfCheck, @@ -100,6 +109,7 @@ void setUp() { expiringCodeStore, approvalStore, scimGroupMembershipManager, + scimUserService, scimUserAliasHandler, transactionTemplate, true, // alias entities are enabled diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java index aee57bb05ab..c0338ae02ae 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsTests.java @@ -32,6 +32,7 @@ import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimGroupProvisioning; import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning; +import org.cloudfoundry.identity.uaa.scim.services.ScimUserService; import org.cloudfoundry.identity.uaa.scim.validate.PasswordValidator; import org.cloudfoundry.identity.uaa.security.IsSelfCheck; import org.cloudfoundry.identity.uaa.test.ZoneSeeder; @@ -216,6 +217,14 @@ void setUpAfterSeeding(final IdentityZone identityZone) { .then(invocationOnMock -> invocationOnMock.getArgument(0)); when(scimUserAliasHandler.retrieveAliasEntity(any())).thenReturn(Optional.empty()); + final ScimUserService scimUserService = new ScimUserService( + scimUserAliasHandler, + jdbcScimUserProvisioning, + identityZoneManager, + transactionTemplate, + false + ); + scimUserEndpoints = new ScimUserEndpoints( new IdentityZoneManagerImpl(), new IsSelfCheck(null), @@ -227,6 +236,7 @@ void setUpAfterSeeding(final IdentityZone identityZone) { null, mockApprovalStore, spiedScimGroupMembershipManager, + scimUserService, scimUserAliasHandler, transactionTemplate, false, @@ -721,7 +731,7 @@ void findUsersApprovalsNotSyncedIfNotIncluded() { void whenSettingAnInvalidUserMaxCount_ScimUsersEndpointShouldThrowAnException() { assertThrowsWithMessageThat( IllegalArgumentException.class, - () -> new ScimUserEndpoints(null, null, null, null, null, null, null, null, null, null, null, null, false, 0), + () -> new ScimUserEndpoints(null, null, null, null, null, null, null, null, null, null, null, null, null, false, 0), containsString("Invalid \"userMaxCount\" value (got 0). Should be positive number.")); } @@ -729,7 +739,7 @@ void whenSettingAnInvalidUserMaxCount_ScimUsersEndpointShouldThrowAnException() void whenSettingANegativeValueUserMaxCount_ScimUsersEndpointShouldThrowAnException() { assertThrowsWithMessageThat( IllegalArgumentException.class, - () -> new ScimUserEndpoints(null, null, null, null, null, null, null, null, null, null, null, null, false, -1), + () -> new ScimUserEndpoints(null, null, null, null, null, null, null, null, null, null, null, null, null, false, -1), containsString("Invalid \"userMaxCount\" value (got -1). Should be positive number.")); } From 6ee2294bc165a5f2ae4baffdb0c05551e4d68e9c Mon Sep 17 00:00:00 2001 From: Adrian Hoelzl Date: Fri, 23 Aug 2024 09:27:26 +0200 Subject: [PATCH 03/13] Add injection of ScimUserService into ScimUserBootstrap --- .../uaa/scim/bootstrap/ScimUserBootstrap.java | 4 + .../LoginSamlAuthenticationProviderTests.java | 22 ++++- .../bootstrap/ScimUserBootstrapTests.java | 80 +++++++++++-------- 3 files changed, 71 insertions(+), 35 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java index 01d8dbb8e87..648c4a60216 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java @@ -12,6 +12,7 @@ import org.cloudfoundry.identity.uaa.scim.exception.MemberAlreadyExistsException; import org.cloudfoundry.identity.uaa.scim.exception.MemberNotFoundException; import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException; +import org.cloudfoundry.identity.uaa.scim.services.ScimUserService; import org.cloudfoundry.identity.uaa.user.UaaUser; import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder; import org.slf4j.Logger; @@ -49,6 +50,7 @@ public class ScimUserBootstrap implements private static final Logger logger = LoggerFactory.getLogger(ScimUserBootstrap.class); private final ScimUserProvisioning scimUserProvisioning; + private final ScimUserService scimUserService; private final ScimGroupProvisioning scimGroupProvisioning; private final ScimGroupMembershipManager membershipManager; private final Collection users; @@ -62,12 +64,14 @@ public class ScimUserBootstrap implements * @param override Flag to indicate that user accounts can be updated as well as created */ public ScimUserBootstrap(final ScimUserProvisioning scimUserProvisioning, + final ScimUserService scimUserService, final ScimGroupProvisioning scimGroupProvisioning, final ScimGroupMembershipManager membershipManager, final Collection users, @Value("${scim.user.override:false}") final boolean override, @Value("${delete.users:#{null}}") final List usersToDelete) { this.scimUserProvisioning = scimUserProvisioning; + this.scimUserService = scimUserService; this.scimGroupProvisioning = scimGroupProvisioning; this.membershipManager = membershipManager; this.users = Collections.unmodifiableCollection(users); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java index ef7bdafb2d0..f3111fd9657 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java @@ -16,12 +16,14 @@ 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.ScimUserAliasHandler; 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.scim.services.ScimUserService; import org.cloudfoundry.identity.uaa.test.TestUtils; import org.cloudfoundry.identity.uaa.user.JdbcUaaUserDatabase; import org.cloudfoundry.identity.uaa.user.UaaAuthority; @@ -200,7 +202,25 @@ void configureProvider() throws SAMLException, SecurityException, DecryptionExce JdbcScimGroupMembershipManager membershipManager = new JdbcScimGroupMembershipManager( jdbcTemplate, new TimeServiceImpl(), userProvisioning, null, dbUtils); membershipManager.setScimGroupProvisioning(groupProvisioning); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(userProvisioning, groupProvisioning, membershipManager, Collections.emptyList(), false, Collections.emptyList()); + + final ScimUserAliasHandler aliasHandler = mock(ScimUserAliasHandler.class); + when(aliasHandler.aliasPropertiesAreValid(any(), any())).thenReturn(true); + + ScimUserBootstrap bootstrap = new ScimUserBootstrap( + userProvisioning, + new ScimUserService( + aliasHandler, + userProvisioning, + identityZoneManager, + null, // not required since alias is disabled + false + ), + groupProvisioning, + membershipManager, + Collections.emptyList(), + false, + Collections.emptyList() + ); externalManager = new JdbcScimGroupExternalMembershipManager(jdbcTemplate, dbUtils); externalManager.setScimGroupProvisioning(groupProvisioning); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java index bbb04317ab8..edae6306dca 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java @@ -159,9 +159,12 @@ void tearDown(@Autowired ApplicationContext applicationContext) throws SQLExcept @Test void canDeleteUsersButOnlyInDefaultZone() throws Exception { String randomZoneId = "randomZoneId-" + new RandomValueStringGenerator().generate().toLowerCase(); - canAddUsers(OriginKeys.UAA, IdentityZone.getUaaZoneId(), jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager); - canAddUsers(OriginKeys.LDAP, IdentityZone.getUaaZoneId(), jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager); - canAddUsers(OriginKeys.UAA, randomZoneId, jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager); //this is just an update of the same two users, zoneId is ignored + canAddUsers(OriginKeys.UAA, IdentityZone.getUaaZoneId(), jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, + scimUserService); + canAddUsers(OriginKeys.LDAP, IdentityZone.getUaaZoneId(), jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, + scimUserService); + canAddUsers(OriginKeys.UAA, randomZoneId, jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, + scimUserService); //this is just an update of the same two users, zoneId is ignored List users = jdbcScimUserProvisioning.retrieveAll(IdentityZone.getUaaZoneId()); assertEquals(4, users.size()); reset(jdbcScimUserProvisioning); @@ -174,7 +177,7 @@ void canDeleteUsersButOnlyInDefaultZone() throws Exception { .when(publisher).publishEvent(any(EntityDeletedEvent.class)); List usersToDelete = Arrays.asList("joe", "mabel", "non-existent"); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, emptyList(), false, usersToDelete); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, emptyList(), false, usersToDelete); bootstrap.setApplicationEventPublisher(publisher); bootstrap.afterPropertiesSet(); bootstrap.onApplicationEvent(mock(ContextRefreshedEvent.class)); @@ -191,7 +194,7 @@ void canDeleteUsersButOnlyInDefaultZone() throws Exception { void slatedForDeleteDoesNotAdd() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); UaaUser mabel = new UaaUser("mabel", "password", "mabel@blah.com", "Mabel", "User"); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Arrays.asList(joe, mabel), false, Arrays.asList("joe", "mabel")); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Arrays.asList(joe, mabel), false, Arrays.asList("joe", "mabel")); bootstrap.afterPropertiesSet(); String zoneId = IdentityZone.getUaaZoneId(); verify(jdbcScimUserProvisioning, never()).create(any(), eq(zoneId)); @@ -201,7 +204,8 @@ void slatedForDeleteDoesNotAdd() { @Test void canAddUsers() throws Exception { - canAddUsers(OriginKeys.UAA, IdentityZone.getUaaZoneId(), jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager); + canAddUsers(OriginKeys.UAA, IdentityZone.getUaaZoneId(), jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, + scimUserService); Collection users = jdbcScimUserProvisioning.retrieveAll(IdentityZone.getUaaZoneId()); assertEquals(2, users.size()); } @@ -209,7 +213,7 @@ void canAddUsers() throws Exception { @Test void addedUsersAreVerified() { UaaUser uaaJoe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(uaaJoe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(uaaJoe), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); @@ -223,7 +227,7 @@ void addedUsersAreVerified() { void canAddUserWithAuthorities() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); joe = joe.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("openid,read")); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); @SuppressWarnings("unchecked") Collection> users = (Collection>) scimUserEndpoints.findUsers("id", @@ -240,7 +244,7 @@ void canAddUserWithAuthorities() { void cannotAddUserWithNoPassword() { UaaUser joe = new UaaUser("joe", "", "joe@test.org", "Joe", "User", OriginKeys.UAA, null); joe = joe.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("openid,read")); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); assertThrows(InvalidPasswordException.class, bootstrap::afterPropertiesSet); } @@ -248,10 +252,10 @@ void cannotAddUserWithNoPassword() { void noOverrideByDefault() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); joe = joe.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("openid,read")); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); joe = new UaaUser("joe", "password", "joe@test.org", "Joel", "User"); - bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); @SuppressWarnings("unchecked") Collection> users = (Collection>) scimUserEndpoints.findUsers("id", @@ -268,10 +272,10 @@ void noOverrideByDefault() { void canOverride() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); joe = joe.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("openid,read")); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); joe = new UaaUser("joe", "password", "joe@test.org", "Joel", "User"); - bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList()); + bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList()); bootstrap.afterPropertiesSet(); @SuppressWarnings("unchecked") Collection> users = (Collection>) scimUserEndpoints.findUsers("id", @@ -288,10 +292,10 @@ void canOverride() { void canOverrideAuthorities() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); joe = joe.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("openid,read")); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); joe = joe.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("openid,read,write")); - bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList()); + bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList()); bootstrap.afterPropertiesSet(); @SuppressWarnings("unchecked") Collection> users = (Collection>) scimUserEndpoints.findUsers("id", @@ -310,11 +314,11 @@ void canRemoveAuthorities() { String joeUserId = "joe" + randomValueStringGenerator.generate(); UaaUser joe = new UaaUser(joeUserId, "password", "joe@test.org", "Joe", "User"); joe = joe.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("openid,read")); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); joe = joe.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("openid")); System.err.println(jdbcTemplate.queryForList("SELECT * FROM group_membership")); - bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList()); + bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList()); bootstrap.afterPropertiesSet(); List users = jdbcScimUserProvisioning.query("userName eq \"" + joeUserId + "\"", IdentityZone.getUaaZoneId()); @@ -329,14 +333,14 @@ void canRemoveAuthorities() { void canUpdateUsers() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); joe = joe.modifyOrigin(OriginKeys.UAA); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); String passwordHash = jdbcTemplate.queryForObject("select password from users where username='joe'", new Object[0], String.class); joe = new UaaUser("joe", "new", "joe@test.org", "Joe", "Bloggs"); joe = joe.modifyOrigin(OriginKeys.UAA); - bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList()); + bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList()); bootstrap.afterPropertiesSet(); Collection users = jdbcScimUserProvisioning.retrieveAll(IdentityZone.getUaaZoneId()); assertEquals(1, users.size()); @@ -351,10 +355,10 @@ void canUpdateUsers() { @Test void unsuccessfulAttemptToUpdateUsersNotFatal() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); joe = new UaaUser("joe", "new", "joe@test.org", "Joe", "Bloggs"); - bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); Collection users = jdbcScimUserProvisioning.retrieveAll(IdentityZone.getUaaZoneId()); assertEquals(1, users.size()); @@ -365,14 +369,14 @@ void unsuccessfulAttemptToUpdateUsersNotFatal() { void updateUserWithEmptyPasswordDoesNotChangePassword() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); joe = joe.modifyOrigin(OriginKeys.UAA); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); String passwordHash = jdbcTemplate.queryForObject("select password from users where username='joe'", new Object[0], String.class); joe = new UaaUser("joe", "", "joe@test.org", "Joe", "Bloggs"); joe = joe.modifyOrigin(OriginKeys.UAA); - bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList()); + bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList()); bootstrap.afterPropertiesSet(); Collection users = jdbcScimUserProvisioning.retrieveAll(IdentityZone.getUaaZoneId()); assertEquals(1, users.size()); @@ -391,7 +395,7 @@ void uaaUserGetsVerifiedSetToTrue() { String username = new RandomValueStringGenerator().generate().toLowerCase(); UaaUser user = getUaaUser(new String[0], origin, email, firstName, lastName, password, externalId, "not-used-id", username); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); ScimUser existingUser = jdbcScimUserProvisioning.retrieveAll(IdentityZone.getUaaZoneId()) @@ -423,7 +427,7 @@ void externalInvitedUserGetsVerifiedSetToFalse() { String username = new RandomValueStringGenerator().generate().toLowerCase(); UaaUser user = getUaaUser(new String[0], origin, email, firstName, lastName, password, externalId, "not-used-id", username); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); ScimUser existingUser = jdbcScimUserProvisioning.retrieveAll(IdentityZone.getUaaZoneId()) @@ -445,12 +449,14 @@ void externalInvitedUserGetsVerifiedSetToFalse() { @Test void canAddNonExistentGroupThroughEvent() throws Exception { - nonExistentGroupThroughEvent(true, jdbcTemplate, jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager); + nonExistentGroupThroughEvent(true, jdbcTemplate, jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, + scimUserService); } @Test void doNotAddNonExistentUsers() throws Exception { - nonExistentGroupThroughEvent(false, jdbcTemplate, jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager); + nonExistentGroupThroughEvent(false, jdbcTemplate, jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, + scimUserService); } @Test @@ -468,7 +474,7 @@ void canUpdateEmailThroughEvent() { String userId = new RandomValueStringGenerator().generate(); String username = new RandomValueStringGenerator().generate(); UaaUser user = getUaaUser(userAuthorities, origin, email, firstName, lastName, password, externalId, userId, username); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); List users = jdbcScimUserProvisioning.query("userName eq \"" + username + "\" and origin eq \"" + origin + "\"", IdentityZone.getUaaZoneId()); @@ -516,7 +522,7 @@ void testGroupsFromEventAreMadeUnique() { String username = new RandomValueStringGenerator().generate(); UaaUser user = getUaaUser(userAuthorities, origin, email, firstName, lastName, password, externalId, userId, username); JdbcScimGroupMembershipManager spy = spy(jdbcScimGroupMembershipManager); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, spy, Collections.singletonList(user), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, spy, Collections.singletonList(user), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); List users = jdbcScimUserProvisioning.query("userName eq \"" + username + "\" and origin eq \"" + origin + "\"", IdentityZone.getUaaZoneId()); @@ -546,11 +552,11 @@ void addUsersWithSameUsername() { String userId = new RandomValueStringGenerator().generate(); String username = new RandomValueStringGenerator().generate(); UaaUser user = getUaaUser(new String[0], origin, email, firstName, lastName, password, externalId, userId, username); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); addIdentityProvider(jdbcTemplate, "newOrigin"); - bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Arrays.asList(user, user.modifySource("newOrigin", "")), false, Collections.emptyList()); + bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Arrays.asList(user, user.modifySource("newOrigin", "")), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); assertEquals(2, jdbcScimUserProvisioning.retrieveAll(IdentityZone.getUaaZoneId()).size()); } @@ -572,7 +578,7 @@ void concurrentAuthEventsRaceCondition() throws Exception { String userId = new RandomValueStringGenerator().generate(); String username = new RandomValueStringGenerator().generate(); UaaUser user = getUaaUser(userAuthorities, origin, email, firstName, lastName, password, externalId, userId, username); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList()); bootstrap.afterPropertiesSet(); List scimUsers = jdbcScimUserProvisioning.query("userName eq \"" + username + "\" and origin eq \"" + origin + "\"", IdentityZone.getUaaZoneId()); @@ -612,11 +618,14 @@ private static void canAddUsers( String zoneId, JdbcScimUserProvisioning jdbcScimUserProvisioning, JdbcScimGroupProvisioning jdbcScimGroupProvisioning, - JdbcScimGroupMembershipManager jdbcScimGroupMembershipManager) { + JdbcScimGroupMembershipManager jdbcScimGroupMembershipManager, + ScimUserService scimUserService + ) { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User", origin, zoneId); UaaUser mabel = new UaaUser("mabel", "password", "mabel@blah.com", "Mabel", "User", origin, zoneId); ScimUserBootstrap bootstrap = new ScimUserBootstrap( jdbcScimUserProvisioning, + scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Arrays.asList(joe, mabel), @@ -630,7 +639,9 @@ private static void nonExistentGroupThroughEvent( final JdbcTemplate jdbcTemplate, final JdbcScimUserProvisioning jdbcScimUserProvisioning, final JdbcScimGroupProvisioning jdbcScimGroupProvisioning, - final JdbcScimGroupMembershipManager jdbcScimGroupMembershipManager) { + final JdbcScimGroupMembershipManager jdbcScimGroupMembershipManager, + final ScimUserService scimUserService + ) { String[] externalAuthorities = new String[]{"extTest1", "extTest2", "extTest3"}; String[] userAuthorities = new String[]{"usrTest1", "usrTest2", "usrTest3"}; String origin = "testOrigin"; @@ -645,6 +656,7 @@ private static void nonExistentGroupThroughEvent( UaaUser user = getUaaUser(userAuthorities, origin, email, firstName, lastName, password, externalId, userId, username); ScimUserBootstrap bootstrap = new ScimUserBootstrap( jdbcScimUserProvisioning, + scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), From 7aa286f9c178d839182296d90c9d598fb839e843 Mon Sep 17 00:00:00 2001 From: Adrian Hoelzl Date: Fri, 23 Aug 2024 09:32:45 +0200 Subject: [PATCH 04/13] Reformat --- .../bootstrap/ScimUserBootstrapTests.java | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java index edae6306dca..bfd894c0d7a 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java @@ -159,12 +159,13 @@ void tearDown(@Autowired ApplicationContext applicationContext) throws SQLExcept @Test void canDeleteUsersButOnlyInDefaultZone() throws Exception { String randomZoneId = "randomZoneId-" + new RandomValueStringGenerator().generate().toLowerCase(); - canAddUsers(OriginKeys.UAA, IdentityZone.getUaaZoneId(), jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, - scimUserService); - canAddUsers(OriginKeys.LDAP, IdentityZone.getUaaZoneId(), jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, - scimUserService); - canAddUsers(OriginKeys.UAA, randomZoneId, jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, - scimUserService); //this is just an update of the same two users, zoneId is ignored + canAddUsers(OriginKeys.UAA, IdentityZone.getUaaZoneId(), jdbcScimUserProvisioning, jdbcScimGroupProvisioning, + jdbcScimGroupMembershipManager, scimUserService); + canAddUsers(OriginKeys.LDAP, IdentityZone.getUaaZoneId(), jdbcScimUserProvisioning, jdbcScimGroupProvisioning, + jdbcScimGroupMembershipManager, scimUserService); + //this is just an update of the same two users, zoneId is ignored + canAddUsers(OriginKeys.UAA, randomZoneId, jdbcScimUserProvisioning, jdbcScimGroupProvisioning, + jdbcScimGroupMembershipManager, scimUserService); List users = jdbcScimUserProvisioning.retrieveAll(IdentityZone.getUaaZoneId()); assertEquals(4, users.size()); reset(jdbcScimUserProvisioning); @@ -204,8 +205,7 @@ void slatedForDeleteDoesNotAdd() { @Test void canAddUsers() throws Exception { - canAddUsers(OriginKeys.UAA, IdentityZone.getUaaZoneId(), jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, - scimUserService); + canAddUsers(OriginKeys.UAA, IdentityZone.getUaaZoneId(), jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, scimUserService); Collection users = jdbcScimUserProvisioning.retrieveAll(IdentityZone.getUaaZoneId()); assertEquals(2, users.size()); } @@ -449,14 +449,12 @@ void externalInvitedUserGetsVerifiedSetToFalse() { @Test void canAddNonExistentGroupThroughEvent() throws Exception { - nonExistentGroupThroughEvent(true, jdbcTemplate, jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, - scimUserService); + nonExistentGroupThroughEvent(true, jdbcTemplate, jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, scimUserService); } @Test void doNotAddNonExistentUsers() throws Exception { - nonExistentGroupThroughEvent(false, jdbcTemplate, jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, - scimUserService); + nonExistentGroupThroughEvent(false, jdbcTemplate, jdbcScimUserProvisioning, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, scimUserService); } @Test From 4e4d82087791fd9b630d1bae0fd8e106ee3b6eea Mon Sep 17 00:00:00 2001 From: Adrian Hoelzl Date: Fri, 23 Aug 2024 09:37:02 +0200 Subject: [PATCH 05/13] Add handling of alias users in ScimUserBootstrap --- .../uaa/scim/bootstrap/ScimUserBootstrap.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java index 648c4a60216..71bb4f82390 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java @@ -164,7 +164,18 @@ private void updateUser(ScimUser existingUser, UaaUser updatedUser, boolean upda final ScimUser newScimUser = convertToScimUser(updatedUser); newScimUser.setVersion(existingUser.getVersion()); - scimUserProvisioning.update(id, newScimUser, IdentityZoneHolder.get().getId()); + newScimUser.setZoneId(existingUser.getZoneId()); + + /* the user in the event won't have the alias properties set, we must therefore propagate them from the existing + * user, if present */ + if (hasText(existingUser.getAliasId()) && hasText(existingUser.getAliasZid())) { + newScimUser.setAliasId(existingUser.getAliasId()); + newScimUser.setAliasZid(existingUser.getAliasZid()); + } + + // this will also handle the update of the alias user, if necessary + scimUserService.updateUser(id, newScimUser); + if (OriginKeys.UAA.equals(newScimUser.getOrigin()) && hasText(updatedUser.getPassword())) { //password is not relevant for non UAA users scimUserProvisioning.changePassword(id, null, updatedUser.getPassword(), IdentityZoneHolder.get().getId()); } From e7fc7655ee8cfb829d9a5c8ff65151946e78652e Mon Sep 17 00:00:00 2001 From: Adrian Hoelzl Date: Fri, 23 Aug 2024 09:58:00 +0200 Subject: [PATCH 06/13] Fix ScimUserEndpointsAliasMockMvcTests --- .../scim/endpoints/ScimUserEndpointsAliasMockMvcTests.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsAliasMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsAliasMockMvcTests.java index df284a48dc7..429a5d6426c 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsAliasMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/scim/endpoints/ScimUserEndpointsAliasMockMvcTests.java @@ -14,6 +14,7 @@ import org.cloudfoundry.identity.uaa.scim.ScimUser; import org.cloudfoundry.identity.uaa.scim.ScimUserAliasHandler; import org.cloudfoundry.identity.uaa.scim.jdbc.JdbcScimUserProvisioning; +import org.cloudfoundry.identity.uaa.scim.services.ScimUserService; import org.cloudfoundry.identity.uaa.util.JsonUtils; import org.cloudfoundry.identity.uaa.zone.IdentityZone; import org.cloudfoundry.identity.uaa.zone.IdentityZoneConfiguration; @@ -55,6 +56,7 @@ public class ScimUserEndpointsAliasMockMvcTests extends AliasMockMvcTestBase { private IdentityProviderEndpoints identityProviderEndpoints; private ScimUserAliasHandler scimUserAliasHandler; private ScimUserEndpoints scimUserEndpoints; + private ScimUserService scimUserService; @BeforeEach void setUp() throws Exception { @@ -64,6 +66,7 @@ void setUp() throws Exception { identityProviderEndpoints = requireNonNull(webApplicationContext.getBean(IdentityProviderEndpoints.class)); scimUserAliasHandler = requireNonNull(webApplicationContext.getBean(ScimUserAliasHandler.class)); scimUserEndpoints = requireNonNull(webApplicationContext.getBean(ScimUserEndpoints.class)); + scimUserService = requireNonNull(webApplicationContext.getBean(ScimUserService.class)); } @Nested @@ -1707,5 +1710,6 @@ protected void arrangeAliasFeatureEnabled(final boolean enabled) { ReflectionTestUtils.setField(identityProviderEndpoints, "aliasEntitiesEnabled", enabled); ReflectionTestUtils.setField(scimUserAliasHandler, "aliasEntitiesEnabled", enabled); ReflectionTestUtils.setField(scimUserEndpoints, "aliasEntitiesEnabled", enabled); + ReflectionTestUtils.setField(scimUserService, "aliasEntitiesEnabled", enabled); } } From f2612efc97535d11c3f9e803a46c49ca9d42444b Mon Sep 17 00:00:00 2001 From: Adrian Hoelzl Date: Fri, 23 Aug 2024 16:27:09 +0200 Subject: [PATCH 07/13] Add test for new behavior to ScimUserBootstrapTests --- .../bootstrap/ScimUserBootstrapTests.java | 136 +++++++++++++++++- 1 file changed, 133 insertions(+), 3 deletions(-) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java index bfd894c0d7a..5f9587648bf 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java @@ -22,7 +22,10 @@ import org.cloudfoundry.identity.uaa.scim.services.ScimUserService; import org.cloudfoundry.identity.uaa.security.IsSelfCheck; import org.cloudfoundry.identity.uaa.test.TestUtils; +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.AlphanumericRandomValueStringGenerator; import org.cloudfoundry.identity.uaa.util.beans.DbUtils; import org.cloudfoundry.identity.uaa.util.TimeServiceImpl; import org.cloudfoundry.identity.uaa.zone.IdentityZone; @@ -48,6 +51,10 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.crypto.password.PasswordEncoder; import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.util.StringUtils; import java.sql.SQLException; import java.util.ArrayList; @@ -79,6 +86,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @WithDatabaseContext class ScimUserBootstrapTests { @@ -97,12 +105,15 @@ class ScimUserBootstrapTests { @Autowired private PasswordEncoder passwordEncoder; private ScimUserService scimUserService; + private JdbcIdentityZoneProvisioning identityZoneProvisioning; + private IdentityZoneManager identityZoneManager; + private IdentityProviderProvisioning idpProvisioning; @BeforeEach void init() throws SQLException { JdbcPagingListFactory pagingListFactory = new JdbcPagingListFactory(namedJdbcTemplate, LimitSqlAdapterFactory.getLimitSqlAdapter()); - final IdentityZoneManager identityZoneManager = new IdentityZoneManagerImpl(); - final JdbcIdentityZoneProvisioning identityZoneProvisioning = new JdbcIdentityZoneProvisioning(jdbcTemplate); + identityZoneManager = new IdentityZoneManagerImpl(); + identityZoneProvisioning = new JdbcIdentityZoneProvisioning(jdbcTemplate); jdbcScimUserProvisioning = spy(new JdbcScimUserProvisioning(namedJdbcTemplate, pagingListFactory, passwordEncoder, identityZoneManager, identityZoneProvisioning)); DbUtils dbUtils = new DbUtils(); @@ -110,7 +121,7 @@ void init() throws SQLException { jdbcScimGroupMembershipManager = new JdbcScimGroupMembershipManager( jdbcTemplate, new TimeServiceImpl(), jdbcScimUserProvisioning, null, dbUtils); jdbcScimGroupMembershipManager.setScimGroupProvisioning(jdbcScimGroupProvisioning); - final IdentityProviderProvisioning idpProvisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate); + idpProvisioning = new JdbcIdentityProviderProvisioning(jdbcTemplate); final ScimUserAliasHandler scimUserAliasHandler = new ScimUserAliasHandler( identityZoneProvisioning, jdbcScimUserProvisioning, @@ -352,6 +363,125 @@ void canUpdateUsers() { assertEquals(passwordHash, jdbcTemplate.queryForObject("select password from users where username='joe'", new Object[0], String.class)); } + @Test + void shouldPropagateAliasPropertiesOfExistingUserDuringUpdate() { + // arrange custom zone exists + final String customZoneId = new AlphanumericRandomValueStringGenerator(8).generate(); + final IdentityZone customZone = new IdentityZone(); + customZone.setId(customZoneId); + customZone.setSubdomain(customZoneId); + customZone.setName(customZoneId); + identityZoneProvisioning.create(customZone); + + // arrange that a user with alias exists + final String userName = "john.doe-" + new AlphanumericRandomValueStringGenerator(8).generate(); + final String givenName = "John"; + final String familyName = "Doe"; + final ScimUser scimUser = new ScimUser(null, userName, givenName, familyName); + final ScimUser.Email email = new ScimUser.Email(); + email.setPrimary(true); + final String emailAddress = "john.doe@example.com"; + email.setValue(emailAddress); + scimUser.setEmails(Collections.singletonList(email)); + final String originKey = new AlphanumericRandomValueStringGenerator(8).generate(); + scimUser.setOrigin(originKey); + scimUser.setZoneId(IdentityZone.getUaaZoneId()); + final ScimUser createdOriginalUser = jdbcScimUserProvisioning.createUser(scimUser, "", IdentityZone.getUaaZoneId()); + final String originalUserId = createdOriginalUser.getId(); + assertTrue(StringUtils.hasText(originalUserId)); + + // create an alias of the user in the custom zone + createdOriginalUser.setId(null); + createdOriginalUser.setZoneId(customZoneId); + createdOriginalUser.setAliasId(originalUserId); + createdOriginalUser.setAliasZid(IdentityZone.getUaaZoneId()); + final ScimUser createdAliasUser = jdbcScimUserProvisioning.createUser(createdOriginalUser, "", customZoneId); + final String aliasUserId = createdAliasUser.getId(); + assertTrue(StringUtils.hasText(aliasUserId)); + + // update the original user to point ot the alias user + createdOriginalUser.setId(originalUserId); + createdOriginalUser.setZoneId(IdentityZone.getUaaZoneId()); + createdOriginalUser.setAliasId(aliasUserId); + createdOriginalUser.setAliasZid(customZoneId); + jdbcScimUserProvisioning.update(originalUserId, createdOriginalUser, IdentityZone.getUaaZoneId()); + + // create and emit event that contains the user with changed fields + final String externalId = new AlphanumericRandomValueStringGenerator(8).generate(); + final String phoneNumber = "12345"; + final UaaUserPrototype userPrototype = new UaaUserPrototype() + .withVerified(true) + .withUsername(userName) + .withPassword("") + .withEmail(emailAddress) + .withAuthorities(UaaAuthority.USER_AUTHORITIES) + .withGivenName(givenName) + .withFamilyName(familyName) + .withCreated(new Date()) + .withModified(new Date()) + .withOrigin(originKey) + .withExternalId(externalId) // changed field + .withZoneId(IdentityZone.getUaaZoneId()) + .withPhoneNumber(phoneNumber); // changed field + final UaaUser uaaUser = new UaaUser(userPrototype); + + final ExternalGroupAuthorizationEvent event = new ExternalGroupAuthorizationEvent(uaaUser, true, Collections.emptyList(), true); + final ScimUserBootstrap bootstrap = buildScimUserBootstrapWithAliasEnabled(); + bootstrap.onApplicationEvent(event); + + // should update both users and the alias reference should stay intact + final ScimUser originalUserAfterEvent = jdbcScimUserProvisioning.retrieve(originalUserId, IdentityZone.getUaaZoneId()); + assertEquals(aliasUserId, originalUserAfterEvent.getAliasId()); + assertEquals(customZoneId, originalUserAfterEvent.getAliasZid()); + assertEquals(externalId, originalUserAfterEvent.getExternalId()); + assertEquals(phoneNumber, originalUserAfterEvent.getPhoneNumbers().get(0).getValue()); + + final ScimUser aliasUserAfterEvent = jdbcScimUserProvisioning.retrieve(aliasUserId, customZoneId); + assertEquals(originalUserId, aliasUserAfterEvent.getAliasId()); + assertEquals(IdentityZone.getUaaZoneId(), aliasUserAfterEvent.getAliasZid()); + assertEquals(externalId, aliasUserAfterEvent.getExternalId()); + assertEquals(phoneNumber, aliasUserAfterEvent.getPhoneNumbers().get(0).getValue()); + } + + private ScimUserBootstrap buildScimUserBootstrapWithAliasEnabled() { + final ScimUserService scimUserServiceAliasEnabled = buildScimUserServiceAliasEnabled(); + return new ScimUserBootstrap( + jdbcScimUserProvisioning, + scimUserServiceAliasEnabled, + jdbcScimGroupProvisioning, + jdbcScimGroupMembershipManager, + Collections.emptyList(), + false, + Collections.emptyList() + ); + } + + private ScimUserService buildScimUserServiceAliasEnabled() { + final ScimUserAliasHandler aliasHandlerAliasEnabled = buildScimUserAliasHandlerAliasEnabled(); + final TransactionTemplate txTemplate = mock(TransactionTemplate.class); + when(txTemplate.execute(any())).then(invocationOnMock -> { + final TransactionCallback action = invocationOnMock.getArgument(0); + return action.doInTransaction(mock(TransactionStatus.class)); + }); + return new ScimUserService( + aliasHandlerAliasEnabled, + jdbcScimUserProvisioning, + identityZoneManager, + txTemplate, + true + ); + } + + private ScimUserAliasHandler buildScimUserAliasHandlerAliasEnabled() { + return new ScimUserAliasHandler( + identityZoneProvisioning, + jdbcScimUserProvisioning, + idpProvisioning, + identityZoneManager, + true + ); + } + @Test void unsuccessfulAttemptToUpdateUsersNotFatal() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); From 95c43a6389f47e4234cc47259d0d607d440d8a01 Mon Sep 17 00:00:00 2001 From: Adrian Hoelzl Date: Thu, 29 Aug 2024 15:48:01 +0200 Subject: [PATCH 08/13] Add unit test for ScimUserService.update --- .../scim/services/ScimUserServiceTest.java | 222 ++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 server/src/test/java/org/cloudfoundry/identity/uaa/scim/services/ScimUserServiceTest.java diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/services/ScimUserServiceTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/services/ScimUserServiceTest.java new file mode 100644 index 00000000000..dbfe155533f --- /dev/null +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/services/ScimUserServiceTest.java @@ -0,0 +1,222 @@ +package org.cloudfoundry.identity.uaa.scim.services; + +import org.cloudfoundry.identity.uaa.alias.AliasPropertiesInvalidException; +import org.cloudfoundry.identity.uaa.scim.ScimUser; +import org.cloudfoundry.identity.uaa.scim.ScimUserAliasHandler; +import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning; +import org.cloudfoundry.identity.uaa.util.AlphanumericRandomValueStringGenerator; +import org.cloudfoundry.identity.uaa.zone.beans.IdentityZoneManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; +import org.springframework.transaction.support.TransactionTemplate; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import java.util.List; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ScimUserServiceTest { + @Mock + private ScimUserAliasHandler scimUserAliasHandler; + + @Mock + private ScimUserProvisioning scimUserProvisioning; + + @Mock + private IdentityZoneManager identityZoneManager; + + @Mock + private TransactionTemplate transactionTemplate; + + private ScimUserService scimUserService; + + private static final AlphanumericRandomValueStringGenerator RANDOM_STRING_GENERATOR = + new AlphanumericRandomValueStringGenerator(8); + + private final String idzId = RANDOM_STRING_GENERATOR.generate(); + private final String userId = RANDOM_STRING_GENERATOR.generate(); + private final String origin = RANDOM_STRING_GENERATOR.generate(); + + @BeforeEach + void setUp() { + // mock current IdZ + when(identityZoneManager.getCurrentIdentityZoneId()).thenReturn(idzId); + } + + /** + * Test cases for both alias entities being enabled and disabled. + */ + private abstract class Base { + @Test + final void testUpdate_ShouldThrow_WhenAliasPropertiesAreInvalid() { + // mock existing user + final ScimUser existingUser = mock(ScimUser.class); + when(scimUserProvisioning.retrieve(userId, idzId)).thenReturn(existingUser); + + // arrange alias properties are invalid + final ScimUser user = mock(ScimUser.class); + when(scimUserAliasHandler.aliasPropertiesAreValid(user, existingUser)).thenReturn(false); + + assertThatExceptionOfType(AliasPropertiesInvalidException.class) + .isThrownBy(() -> scimUserService.updateUser(userId, user)); + } + } + + @Nested + class AliasEntitiesEnabled extends Base { + @BeforeEach + void setUp() { + scimUserService = new ScimUserService( + scimUserAliasHandler, + scimUserProvisioning, + identityZoneManager, + transactionTemplate, + true + ); + + // mock transaction template + lenient().when(transactionTemplate.execute(ArgumentMatchers.any())) + .then(invocationOnMock -> { + final TransactionCallback callback = invocationOnMock.getArgument(0); + return callback.doInTransaction(mock(TransactionStatus.class)); + }); + } + + @Test + void testUpdate_ShouldAlsoUpdateAlias_WhenAliasPropertiesAreValid() { + // mock existing user + final ScimUser existingUser = buildExemplaryUser(userId, idzId, origin); + when(scimUserProvisioning.retrieve(userId, idzId)).thenReturn(existingUser); + + // arrange alias properties are valid + final ScimUser user = cloneScimUser(existingUser); + user.setUserName("%s-updated".formatted(user.getUserName())); + when(scimUserAliasHandler.aliasPropertiesAreValid(user, existingUser)).thenReturn(true); + + // arrange update of original user + final ScimUser updatedOriginalUser = mock(ScimUser.class); + when(scimUserProvisioning.update(userId, user, idzId)).thenReturn(updatedOriginalUser); + + scimUserService.updateUser(userId, user); + + // scimUserProvisioning.update should be called exactly once + verify(scimUserProvisioning, times(1)).update(userId, user, idzId); + + // the scim alias handler should be called + verify(scimUserAliasHandler, times(1)).ensureConsistencyOfAliasEntity( + updatedOriginalUser, + existingUser + ); + } + } + + @Nested + class AliasEntitiesDisabled extends Base { + @BeforeEach + void setUp() { + scimUserService = new ScimUserService( + scimUserAliasHandler, + scimUserProvisioning, + identityZoneManager, + transactionTemplate, + false + ); + } + + @Test + void testUpdate_ShouldUpdateOnlyOriginalUser_WhenAliasEnabledAndPropertiesAreValid() { + // mock existing user + final ScimUser existingUser = buildExemplaryUser(userId, idzId, origin); + when(scimUserProvisioning.retrieve(userId, idzId)).thenReturn(existingUser); + + // arrange alias properties are valid + final ScimUser user = cloneScimUser(existingUser); + user.setUserName("%s-updated".formatted(user.getUserName())); + when(scimUserAliasHandler.aliasPropertiesAreValid(user, existingUser)).thenReturn(true); + + scimUserService.updateUser(userId, user); + + // scimUserProvisioning.update should be called exactly once + verify(scimUserProvisioning, times(1)).update(userId, user, idzId); + + // the scim alias handler should not be called + verify(scimUserAliasHandler, never()).ensureConsistencyOfAliasEntity(any(), any()); + } + } + + private static ScimUser buildExemplaryUser( + @Nullable final String id, + @Nonnull final String idzId, + @Nonnull final String origin + ) { + final ScimUser user = new ScimUser(); + user.setId(id); + user.setZoneId(idzId); + user.setName(new ScimUser.Name("John", "Doe")); + final String userName = "john.doe." + RANDOM_STRING_GENERATOR.generate(); + user.setUserName(userName); + final ScimUser.Email email = new ScimUser.Email(); + email.setPrimary(true); + email.setValue("%s@example.com".formatted(userName)); + user.setEmails(singletonList(email)); + user.setActive(true); + user.setOrigin(origin); + return user; + } + + private static ScimUser cloneScimUser(final ScimUser user) { + final ScimUser clone = new ScimUser(); + clone.setId(user.getId()); + clone.setExternalId(user.getExternalId()); + clone.setUserName(user.getUserName()); + clone.setEmails(user.getEmails().stream().map(it -> { + final ScimUser.Email email = new ScimUser.Email(); + email.setValue(it.getValue()); + email.setPrimary(it.isPrimary()); + email.setType(it.getType()); + return email; + }).toList()); + clone.setName(new ScimUser.Name(user.getName().getGivenName(), user.getName().getFamilyName())); + final List clonedPhoneNumbers; + if (user.getPhoneNumbers() == null) { + clonedPhoneNumbers = null; + } else { + clonedPhoneNumbers = user.getPhoneNumbers().stream().map(it -> { + final ScimUser.PhoneNumber phoneNumber = new ScimUser.PhoneNumber(); + phoneNumber.setType(it.getType()); + phoneNumber.setValue(it.getValue()); + return phoneNumber; + }).toList(); + } + clone.setPhoneNumbers(clonedPhoneNumbers); + clone.setActive(user.isActive()); + clone.setOrigin(user.getOrigin()); + clone.setAliasId(user.getAliasId()); + clone.setAliasZid(user.getAliasZid()); + clone.setZoneId(user.getZoneId()); + clone.setPassword(user.getPassword()); + clone.setSalt(user.getSalt()); + clone.setLastLogonTime(user.getLastLogonTime()); + clone.setPasswordLastModified(user.getPasswordLastModified()); + clone.setPreviousLogonTime(user.getPreviousLogonTime()); + return clone; + } +} \ No newline at end of file From 8b044e9b63266e94951cb5831f4685172ccae4aa Mon Sep 17 00:00:00 2001 From: Adrian Hoelzl Date: Wed, 4 Sep 2024 14:21:28 +0200 Subject: [PATCH 09/13] Add documentation for alias user handling during logon --- docs/UAA-Alias-Entities.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/UAA-Alias-Entities.md b/docs/UAA-Alias-Entities.md index 4d1d8ae70ca..f7fd58e1af3 100644 --- a/docs/UAA-Alias-Entities.md +++ b/docs/UAA-Alias-Entities.md @@ -115,9 +115,13 @@ Please note that disabling the flag does not lead to existing entities with alia In addition to enabling the alias feature, one must ensure that no groups can be created that would give users inside a custom zone any authorizations in other zones (e.g., `zones..admin`). This can be achieved by using the allow list for groups (`userConfig.allowedGroups`) in the configuration of the -identity zone. +identity zone. +## User Logon +During logon, the information of the matching shadow user is updated with the information from the identity provider +(e.g., the ID token in the OpenID Connect flow). +If this shadow user has an alias, the updated properties are propagated to it. From f078da550e18daec2516f837ce0d040acea7503e Mon Sep 17 00:00:00 2001 From: Adrian Hoelzl Date: Wed, 4 Sep 2024 15:07:34 +0200 Subject: [PATCH 10/13] Inject 'aliasEntitiesEnabled into ScimUserBootstrap' --- .../uaa/scim/bootstrap/ScimUserBootstrap.java | 20 +++--- .../LoginSamlAuthenticationProviderTests.java | 3 +- .../bootstrap/ScimUserBootstrapTests.java | 63 ++++++++++--------- 3 files changed, 49 insertions(+), 37 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java index 71bb4f82390..6d428acbb62 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java @@ -18,6 +18,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; @@ -56,6 +57,7 @@ public class ScimUserBootstrap implements private final Collection users; private final boolean override; private final List usersToDelete; + private final boolean aliasEntitiesEnabled; private ApplicationEventPublisher publisher; /** @@ -63,13 +65,16 @@ public class ScimUserBootstrap implements * @param users Users to create * @param override Flag to indicate that user accounts can be updated as well as created */ - public ScimUserBootstrap(final ScimUserProvisioning scimUserProvisioning, - final ScimUserService scimUserService, - final ScimGroupProvisioning scimGroupProvisioning, - final ScimGroupMembershipManager membershipManager, - final Collection users, - @Value("${scim.user.override:false}") final boolean override, - @Value("${delete.users:#{null}}") final List usersToDelete) { + public ScimUserBootstrap( + final ScimUserProvisioning scimUserProvisioning, + final ScimUserService scimUserService, + final ScimGroupProvisioning scimGroupProvisioning, + final ScimGroupMembershipManager membershipManager, + final Collection users, + @Value("${scim.user.override:false}") final boolean override, + @Value("${delete.users:#{null}}") final List usersToDelete, + @Qualifier("aliasEntitiesEnabled") final boolean aliasEntitiesEnabled + ) { this.scimUserProvisioning = scimUserProvisioning; this.scimUserService = scimUserService; this.scimGroupProvisioning = scimGroupProvisioning; @@ -77,6 +82,7 @@ public ScimUserBootstrap(final ScimUserProvisioning scimUserProvisioning, this.users = Collections.unmodifiableCollection(users); this.override = override; this.usersToDelete = usersToDelete; + this.aliasEntitiesEnabled = aliasEntitiesEnabled; } @Override diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java index f3111fd9657..bf7c108396c 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/provider/saml/LoginSamlAuthenticationProviderTests.java @@ -219,7 +219,8 @@ void configureProvider() throws SAMLException, SecurityException, DecryptionExce membershipManager, Collections.emptyList(), false, - Collections.emptyList() + Collections.emptyList(), + false ); externalManager = new JdbcScimGroupExternalMembershipManager(jdbcTemplate, dbUtils); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java index 5f9587648bf..e6821fa2ef8 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java @@ -189,7 +189,7 @@ void canDeleteUsersButOnlyInDefaultZone() throws Exception { .when(publisher).publishEvent(any(EntityDeletedEvent.class)); List usersToDelete = Arrays.asList("joe", "mabel", "non-existent"); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, emptyList(), false, usersToDelete); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, emptyList(), false, usersToDelete, false); bootstrap.setApplicationEventPublisher(publisher); bootstrap.afterPropertiesSet(); bootstrap.onApplicationEvent(mock(ContextRefreshedEvent.class)); @@ -206,7 +206,7 @@ void canDeleteUsersButOnlyInDefaultZone() throws Exception { void slatedForDeleteDoesNotAdd() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); UaaUser mabel = new UaaUser("mabel", "password", "mabel@blah.com", "Mabel", "User"); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Arrays.asList(joe, mabel), false, Arrays.asList("joe", "mabel")); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Arrays.asList(joe, mabel), false, Arrays.asList("joe", "mabel"), false); bootstrap.afterPropertiesSet(); String zoneId = IdentityZone.getUaaZoneId(); verify(jdbcScimUserProvisioning, never()).create(any(), eq(zoneId)); @@ -224,7 +224,7 @@ void canAddUsers() throws Exception { @Test void addedUsersAreVerified() { UaaUser uaaJoe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(uaaJoe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(uaaJoe), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); @@ -238,7 +238,7 @@ void addedUsersAreVerified() { void canAddUserWithAuthorities() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); joe = joe.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("openid,read")); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); @SuppressWarnings("unchecked") Collection> users = (Collection>) scimUserEndpoints.findUsers("id", @@ -255,7 +255,7 @@ void canAddUserWithAuthorities() { void cannotAddUserWithNoPassword() { UaaUser joe = new UaaUser("joe", "", "joe@test.org", "Joe", "User", OriginKeys.UAA, null); joe = joe.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("openid,read")); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList(), false); assertThrows(InvalidPasswordException.class, bootstrap::afterPropertiesSet); } @@ -263,10 +263,10 @@ void cannotAddUserWithNoPassword() { void noOverrideByDefault() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); joe = joe.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("openid,read")); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); joe = new UaaUser("joe", "password", "joe@test.org", "Joel", "User"); - bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); @SuppressWarnings("unchecked") Collection> users = (Collection>) scimUserEndpoints.findUsers("id", @@ -283,10 +283,10 @@ void noOverrideByDefault() { void canOverride() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); joe = joe.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("openid,read")); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); joe = new UaaUser("joe", "password", "joe@test.org", "Joel", "User"); - bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList()); + bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); @SuppressWarnings("unchecked") Collection> users = (Collection>) scimUserEndpoints.findUsers("id", @@ -303,10 +303,10 @@ void canOverride() { void canOverrideAuthorities() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); joe = joe.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("openid,read")); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); joe = joe.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("openid,read,write")); - bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList()); + bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); @SuppressWarnings("unchecked") Collection> users = (Collection>) scimUserEndpoints.findUsers("id", @@ -325,11 +325,11 @@ void canRemoveAuthorities() { String joeUserId = "joe" + randomValueStringGenerator.generate(); UaaUser joe = new UaaUser(joeUserId, "password", "joe@test.org", "Joe", "User"); joe = joe.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("openid,read")); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); joe = joe.authorities(AuthorityUtils.commaSeparatedStringToAuthorityList("openid")); System.err.println(jdbcTemplate.queryForList("SELECT * FROM group_membership")); - bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList()); + bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); List users = jdbcScimUserProvisioning.query("userName eq \"" + joeUserId + "\"", IdentityZone.getUaaZoneId()); @@ -344,14 +344,14 @@ void canRemoveAuthorities() { void canUpdateUsers() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); joe = joe.modifyOrigin(OriginKeys.UAA); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); String passwordHash = jdbcTemplate.queryForObject("select password from users where username='joe'", new Object[0], String.class); joe = new UaaUser("joe", "new", "joe@test.org", "Joe", "Bloggs"); joe = joe.modifyOrigin(OriginKeys.UAA); - bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList()); + bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); Collection users = jdbcScimUserProvisioning.retrieveAll(IdentityZone.getUaaZoneId()); assertEquals(1, users.size()); @@ -452,7 +452,8 @@ private ScimUserBootstrap buildScimUserBootstrapWithAliasEnabled() { jdbcScimGroupMembershipManager, Collections.emptyList(), false, - Collections.emptyList() + Collections.emptyList(), + true ); } @@ -485,10 +486,10 @@ private ScimUserAliasHandler buildScimUserAliasHandlerAliasEnabled() { @Test void unsuccessfulAttemptToUpdateUsersNotFatal() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); joe = new UaaUser("joe", "new", "joe@test.org", "Joe", "Bloggs"); - bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); Collection users = jdbcScimUserProvisioning.retrieveAll(IdentityZone.getUaaZoneId()); assertEquals(1, users.size()); @@ -499,14 +500,14 @@ void unsuccessfulAttemptToUpdateUsersNotFatal() { void updateUserWithEmptyPasswordDoesNotChangePassword() { UaaUser joe = new UaaUser("joe", "password", "joe@test.org", "Joe", "User"); joe = joe.modifyOrigin(OriginKeys.UAA); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); String passwordHash = jdbcTemplate.queryForObject("select password from users where username='joe'", new Object[0], String.class); joe = new UaaUser("joe", "", "joe@test.org", "Joe", "Bloggs"); joe = joe.modifyOrigin(OriginKeys.UAA); - bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList()); + bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(joe), true, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); Collection users = jdbcScimUserProvisioning.retrieveAll(IdentityZone.getUaaZoneId()); assertEquals(1, users.size()); @@ -525,7 +526,7 @@ void uaaUserGetsVerifiedSetToTrue() { String username = new RandomValueStringGenerator().generate().toLowerCase(); UaaUser user = getUaaUser(new String[0], origin, email, firstName, lastName, password, externalId, "not-used-id", username); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); ScimUser existingUser = jdbcScimUserProvisioning.retrieveAll(IdentityZone.getUaaZoneId()) @@ -557,7 +558,7 @@ void externalInvitedUserGetsVerifiedSetToFalse() { String username = new RandomValueStringGenerator().generate().toLowerCase(); UaaUser user = getUaaUser(new String[0], origin, email, firstName, lastName, password, externalId, "not-used-id", username); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); ScimUser existingUser = jdbcScimUserProvisioning.retrieveAll(IdentityZone.getUaaZoneId()) @@ -602,7 +603,7 @@ void canUpdateEmailThroughEvent() { String userId = new RandomValueStringGenerator().generate(); String username = new RandomValueStringGenerator().generate(); UaaUser user = getUaaUser(userAuthorities, origin, email, firstName, lastName, password, externalId, userId, username); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); List users = jdbcScimUserProvisioning.query("userName eq \"" + username + "\" and origin eq \"" + origin + "\"", IdentityZone.getUaaZoneId()); @@ -650,7 +651,7 @@ void testGroupsFromEventAreMadeUnique() { String username = new RandomValueStringGenerator().generate(); UaaUser user = getUaaUser(userAuthorities, origin, email, firstName, lastName, password, externalId, userId, username); JdbcScimGroupMembershipManager spy = spy(jdbcScimGroupMembershipManager); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, spy, Collections.singletonList(user), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, spy, Collections.singletonList(user), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); List users = jdbcScimUserProvisioning.query("userName eq \"" + username + "\" and origin eq \"" + origin + "\"", IdentityZone.getUaaZoneId()); @@ -680,11 +681,11 @@ void addUsersWithSameUsername() { String userId = new RandomValueStringGenerator().generate(); String username = new RandomValueStringGenerator().generate(); UaaUser user = getUaaUser(new String[0], origin, email, firstName, lastName, password, externalId, userId, username); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); addIdentityProvider(jdbcTemplate, "newOrigin"); - bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Arrays.asList(user, user.modifySource("newOrigin", "")), false, Collections.emptyList()); + bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Arrays.asList(user, user.modifySource("newOrigin", "")), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); assertEquals(2, jdbcScimUserProvisioning.retrieveAll(IdentityZone.getUaaZoneId()).size()); } @@ -706,7 +707,7 @@ void concurrentAuthEventsRaceCondition() throws Exception { String userId = new RandomValueStringGenerator().generate(); String username = new RandomValueStringGenerator().generate(); UaaUser user = getUaaUser(userAuthorities, origin, email, firstName, lastName, password, externalId, userId, username); - ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList()); + ScimUserBootstrap bootstrap = new ScimUserBootstrap(jdbcScimUserProvisioning, scimUserService, jdbcScimGroupProvisioning, jdbcScimGroupMembershipManager, Collections.singletonList(user), false, Collections.emptyList(), false); bootstrap.afterPropertiesSet(); List scimUsers = jdbcScimUserProvisioning.query("userName eq \"" + username + "\" and origin eq \"" + origin + "\"", IdentityZone.getUaaZoneId()); @@ -758,7 +759,9 @@ private static void canAddUsers( jdbcScimGroupMembershipManager, Arrays.asList(joe, mabel), false, - Collections.emptyList()); + Collections.emptyList(), + false + ); bootstrap.afterPropertiesSet(); } @@ -789,7 +792,9 @@ private static void nonExistentGroupThroughEvent( jdbcScimGroupMembershipManager, Collections.singletonList(user), false, - Collections.emptyList()); + Collections.emptyList(), + false + ); bootstrap.afterPropertiesSet(); List users = jdbcScimUserProvisioning.query("userName eq \"" + username + "\" and origin eq \"" + origin + "\"", IdentityZone.getUaaZoneId()); From a99f3fa344e8e7d6666f88343e934601c3c1d352 Mon Sep 17 00:00:00 2001 From: Adrian Hoelzl Date: Wed, 4 Sep 2024 15:35:40 +0200 Subject: [PATCH 11/13] Handle login of users with alias when alias entities are enabled: only update original user while alias properties remain unchanged --- .../uaa/scim/bootstrap/ScimUserBootstrap.java | 9 +- .../bootstrap/ScimUserBootstrapTests.java | 158 ++++++++++++++---- 2 files changed, 132 insertions(+), 35 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java index 6d428acbb62..d4c557ceb43 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrap.java @@ -179,8 +179,13 @@ private void updateUser(ScimUser existingUser, UaaUser updatedUser, boolean upda newScimUser.setAliasZid(existingUser.getAliasZid()); } - // this will also handle the update of the alias user, if necessary - scimUserService.updateUser(id, newScimUser); + if (aliasEntitiesEnabled) { + // update the user and propagate the changes to the alias, if present + scimUserService.updateUser(id, newScimUser); + } else { + // update only the original user, even if it has an alias (the alias properties remain unchanged) + scimUserProvisioning.update(id, newScimUser, IdentityZoneHolder.get().getId()); + } if (OriginKeys.UAA.equals(newScimUser.getOrigin()) && hasText(updatedUser.getPassword())) { //password is not relevant for non UAA users scimUserProvisioning.changePassword(id, null, updatedUser.getPassword(), IdentityZoneHolder.get().getId()); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java index e6821fa2ef8..4e3d97af736 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java @@ -1,5 +1,6 @@ package org.cloudfoundry.identity.uaa.scim.bootstrap; +import org.apache.commons.lang3.tuple.Triple; import org.cloudfoundry.identity.uaa.annotations.WithDatabaseContext; import org.cloudfoundry.identity.uaa.audit.event.EntityDeletedEvent; import org.cloudfoundry.identity.uaa.authentication.manager.ExternalGroupAuthorizationEvent; @@ -367,44 +368,18 @@ void canUpdateUsers() { void shouldPropagateAliasPropertiesOfExistingUserDuringUpdate() { // arrange custom zone exists final String customZoneId = new AlphanumericRandomValueStringGenerator(8).generate(); - final IdentityZone customZone = new IdentityZone(); - customZone.setId(customZoneId); - customZone.setSubdomain(customZoneId); - customZone.setName(customZoneId); - identityZoneProvisioning.create(customZone); + createCustomZone(customZoneId); - // arrange that a user with alias exists + // create a user with alias + final String originKey = new AlphanumericRandomValueStringGenerator(8).generate(); final String userName = "john.doe-" + new AlphanumericRandomValueStringGenerator(8).generate(); final String givenName = "John"; final String familyName = "Doe"; - final ScimUser scimUser = new ScimUser(null, userName, givenName, familyName); - final ScimUser.Email email = new ScimUser.Email(); - email.setPrimary(true); final String emailAddress = "john.doe@example.com"; - email.setValue(emailAddress); - scimUser.setEmails(Collections.singletonList(email)); - final String originKey = new AlphanumericRandomValueStringGenerator(8).generate(); - scimUser.setOrigin(originKey); - scimUser.setZoneId(IdentityZone.getUaaZoneId()); - final ScimUser createdOriginalUser = jdbcScimUserProvisioning.createUser(scimUser, "", IdentityZone.getUaaZoneId()); - final String originalUserId = createdOriginalUser.getId(); - assertTrue(StringUtils.hasText(originalUserId)); - - // create an alias of the user in the custom zone - createdOriginalUser.setId(null); - createdOriginalUser.setZoneId(customZoneId); - createdOriginalUser.setAliasId(originalUserId); - createdOriginalUser.setAliasZid(IdentityZone.getUaaZoneId()); - final ScimUser createdAliasUser = jdbcScimUserProvisioning.createUser(createdOriginalUser, "", customZoneId); - final String aliasUserId = createdAliasUser.getId(); - assertTrue(StringUtils.hasText(aliasUserId)); - - // update the original user to point ot the alias user - createdOriginalUser.setId(originalUserId); - createdOriginalUser.setZoneId(IdentityZone.getUaaZoneId()); - createdOriginalUser.setAliasId(aliasUserId); - createdOriginalUser.setAliasZid(customZoneId); - jdbcScimUserProvisioning.update(originalUserId, createdOriginalUser, IdentityZone.getUaaZoneId()); + final Triple userIdsAndOriginalUser = createUserWithAlias(customZoneId, originKey, + emailAddress, userName, givenName, familyName); + final String originalUserId = userIdsAndOriginalUser.getLeft(); + final String aliasUserId = userIdsAndOriginalUser.getMiddle(); // create and emit event that contains the user with changed fields final String externalId = new AlphanumericRandomValueStringGenerator(8).generate(); @@ -443,6 +418,123 @@ void shouldPropagateAliasPropertiesOfExistingUserDuringUpdate() { assertEquals(phoneNumber, aliasUserAfterEvent.getPhoneNumbers().get(0).getValue()); } + @Test + public void shouldOnlyUpdateOriginalUser_WhenUserHasAliasButAliasEntitiesDisabled() { + // arrange custom zone exists + final String customZoneId = new AlphanumericRandomValueStringGenerator(8).generate(); + createCustomZone(customZoneId); + + // create a user with alias + final String originKey = new AlphanumericRandomValueStringGenerator(8).generate(); + final String userName = "john.doe-" + new AlphanumericRandomValueStringGenerator(8).generate(); + final String givenName = "John"; + final String familyName = "Doe"; + final String emailAddress = "john.doe@example.com"; + final Triple userIdsAndOriginalUser = createUserWithAlias(customZoneId, originKey, + emailAddress, userName, givenName, familyName); + final String originalUserId = userIdsAndOriginalUser.getLeft(); + final String aliasUserId = userIdsAndOriginalUser.getMiddle(); + final ScimUser originalUser = userIdsAndOriginalUser.getRight(); + + // create and emit event that contains the user with changed fields + final String externalId = new AlphanumericRandomValueStringGenerator(8).generate(); + final String phoneNumber = "12345"; + final UaaUserPrototype userPrototype = new UaaUserPrototype() + .withVerified(true) + .withUsername(userName) + .withPassword("") + .withEmail(emailAddress) + .withAuthorities(UaaAuthority.USER_AUTHORITIES) + .withGivenName(givenName) + .withFamilyName(familyName) + .withCreated(new Date()) + .withModified(new Date()) + .withOrigin(originKey) + .withExternalId(externalId) // changed field + .withZoneId(IdentityZone.getUaaZoneId()) + .withPhoneNumber(phoneNumber); // changed field + final UaaUser uaaUser = new UaaUser(userPrototype); + + final ExternalGroupAuthorizationEvent event = new ExternalGroupAuthorizationEvent(uaaUser, true, Collections.emptyList(), true); + final ScimUserBootstrap bootstrapAliasDisabled = new ScimUserBootstrap( + jdbcScimUserProvisioning, + scimUserService, + jdbcScimGroupProvisioning, + jdbcScimGroupMembershipManager, + Collections.emptyList(), + false, + Collections.emptyList(), + false + ); + bootstrapAliasDisabled.onApplicationEvent(event); + + // should only update the original user and the alias reference should stay intact + final ScimUser originalUserAfterEvent = jdbcScimUserProvisioning.retrieve(originalUserId, IdentityZone.getUaaZoneId()); + assertEquals(aliasUserId, originalUserAfterEvent.getAliasId()); + assertEquals(customZoneId, originalUserAfterEvent.getAliasZid()); + assertEquals(externalId, originalUserAfterEvent.getExternalId()); + assertEquals(phoneNumber, originalUserAfterEvent.getPhoneNumbers().get(0).getValue()); + + assertNotEquals(originalUser.getExternalId(), originalUserAfterEvent.getExternalId()); + assertNotEquals(originalUser.getPhoneNumbers(), originalUserAfterEvent.getPhoneNumbers()); + + final ScimUser aliasUserAfterEvent = jdbcScimUserProvisioning.retrieve(aliasUserId, customZoneId); + assertEquals(originalUserId, aliasUserAfterEvent.getAliasId()); + assertEquals(IdentityZone.getUaaZoneId(), aliasUserAfterEvent.getAliasZid()); + assertEquals(originalUser.getExternalId(), aliasUserAfterEvent.getExternalId()); // should be left unchanged + assertEquals(originalUser.getPhoneNumbers(), aliasUserAfterEvent.getPhoneNumbers()); // should be left unchanged + } + + private void createCustomZone(final String customZoneId) { + final IdentityZone customZone = new IdentityZone(); + customZone.setId(customZoneId); + customZone.setSubdomain(customZoneId); + customZone.setName(customZoneId); + identityZoneProvisioning.create(customZone); + } + + /** + * @return a triple of the original user's ID, the alias user's ID and the original user + */ + private Triple createUserWithAlias( + final String customZoneId, + final String originKey, + final String emailAddress, + final String userName, + final String givenName, + final String familyName + ) { + // arrange that a user with alias exists + final ScimUser scimUser = new ScimUser(null, userName, givenName, familyName); + final ScimUser.Email email = new ScimUser.Email(); + email.setPrimary(true); + email.setValue(emailAddress); + scimUser.setEmails(Collections.singletonList(email)); + scimUser.setOrigin(originKey); + scimUser.setZoneId(IdentityZone.getUaaZoneId()); + final ScimUser createdOriginalUser = jdbcScimUserProvisioning.createUser(scimUser, "", IdentityZone.getUaaZoneId()); + final String originalUserId = createdOriginalUser.getId(); + assertTrue(StringUtils.hasText(originalUserId)); + + // create an alias of the user in the custom zone + createdOriginalUser.setId(null); + createdOriginalUser.setZoneId(customZoneId); + createdOriginalUser.setAliasId(originalUserId); + createdOriginalUser.setAliasZid(IdentityZone.getUaaZoneId()); + final ScimUser createdAliasUser = jdbcScimUserProvisioning.createUser(createdOriginalUser, "", customZoneId); + final String aliasUserId = createdAliasUser.getId(); + assertTrue(StringUtils.hasText(aliasUserId)); + + // update the original user to point ot the alias user + createdOriginalUser.setId(originalUserId); + createdOriginalUser.setZoneId(IdentityZone.getUaaZoneId()); + createdOriginalUser.setAliasId(aliasUserId); + createdOriginalUser.setAliasZid(customZoneId); + final ScimUser originalUser = jdbcScimUserProvisioning.update(originalUserId, createdOriginalUser, IdentityZone.getUaaZoneId()); + + return Triple.of(originalUserId, aliasUserId, originalUser); + } + private ScimUserBootstrap buildScimUserBootstrapWithAliasEnabled() { final ScimUserService scimUserServiceAliasEnabled = buildScimUserServiceAliasEnabled(); return new ScimUserBootstrap( From f653f164a498324ddd8441df666ff284472f4a24 Mon Sep 17 00:00:00 2001 From: Adrian Hoelzl Date: Wed, 4 Sep 2024 15:39:12 +0200 Subject: [PATCH 12/13] Update documentation --- docs/UAA-Alias-Entities.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/UAA-Alias-Entities.md b/docs/UAA-Alias-Entities.md index f7fd58e1af3..f5fdc27da79 100644 --- a/docs/UAA-Alias-Entities.md +++ b/docs/UAA-Alias-Entities.md @@ -121,7 +121,11 @@ identity zone. During logon, the information of the matching shadow user is updated with the information from the identity provider (e.g., the ID token in the OpenID Connect flow). -If this shadow user has an alias, the updated properties are propagated to it. + +If this shadow user has an alias, ... +- *alias entities enabled:* the updated properties are propagated to the alias. +- *alias entities disabled:* only the user itself is updated, the alias user is left unchanged. + - the alias properties are not changed - original and alias user still reference each other From 785a86459c8b2852c7f4914b9d17c4e2ccd53a56 Mon Sep 17 00:00:00 2001 From: Adrian Hoelzl Date: Wed, 4 Sep 2024 16:13:00 +0200 Subject: [PATCH 13/13] Sonar --- .../identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java index 4e3d97af736..0cc637ef5c2 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/scim/bootstrap/ScimUserBootstrapTests.java @@ -419,7 +419,7 @@ void shouldPropagateAliasPropertiesOfExistingUserDuringUpdate() { } @Test - public void shouldOnlyUpdateOriginalUser_WhenUserHasAliasButAliasEntitiesDisabled() { + void shouldOnlyUpdateOriginalUser_WhenUserHasAliasButAliasEntitiesDisabled() { // arrange custom zone exists final String customZoneId = new AlphanumericRandomValueStringGenerator(8).generate(); createCustomZone(customZoneId);