From 49e48c43f4499d0eb29edca81f9e0a511450c9e9 Mon Sep 17 00:00:00 2001 From: Alexander Klimetschek Date: Tue, 20 Sep 2016 17:51:05 -0700 Subject: [PATCH] OAK-4825 - Support disabling of users instead of removal in DefaultSyncHandler --- .../external/basic/DefaultSyncConfig.java | 18 ++++++++++++ .../external/basic/DefaultSyncContext.java | 25 ++++++++++++++-- .../external/impl/DefaultSyncConfigImpl.java | 17 +++++++++++ .../external/TestIdentityProvider.java | 2 +- .../external/basic/DefaultSyncContextTest.java | 33 ++++++++++++++++++++++ 5 files changed, 92 insertions(+), 3 deletions(-) diff --git a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncConfig.java b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncConfig.java index 38736b8..d17174c 100644 --- a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncConfig.java +++ b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncConfig.java @@ -229,6 +229,8 @@ public Authorizable setPathPrefix(@Nonnull String pathPrefix) { private boolean dynamicMembership; + private boolean disableMissing; + /** * Returns the duration in milliseconds until the group membership of a user is expired. If the * membership information is expired it is re-synced according to the maximum nesting depth. @@ -310,6 +312,22 @@ public User setDynamicMembership(boolean dynamicMembership) { return this; } + /** + * Controls the behavior for users that no longer exist on the external provider. The default is to delete the repository users + * if they no longer exist on the external provider. If set to true, they will be disabled instead, and re-enabled once they appear + * again. + */ + public boolean getDisableMissing() { + return disableMissing; + } + + /** + * @see #getDisableMissing() + */ + public User setDisableMissing(boolean disableMissing) { + this.disableMissing = disableMissing; + return this; + } } /** diff --git a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContext.java b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContext.java index a46ff35..4c8834a 100644 --- a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContext.java +++ b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContext.java @@ -325,11 +325,24 @@ private DefaultSyncResultImpl handleMissingIdentity(@Nonnull String id, if (authorizable.isGroup() && ((Group) authorizable).getDeclaredMembers().hasNext()) { log.info("won't remove local group with members: {}", id); status = SyncResult.Status.NOP; + } else if (!keepMissing) { - authorizable.remove(); - log.debug("removing authorizable '{}' that no longer exists on IDP {}", id, idp.getName()); + if (authorizable instanceof User) { + User user = (User) authorizable; + if (config.user().getDisableMissing()) { + user.disable("No longer exists on IDP " + idp.getName()); + log.debug("disabling user '{}' that no longer exists on IDP {}", id, idp.getName()); + } else { + user.remove(); + log.debug("removing user '{}' that no longer exists on IDP {}", id, idp.getName()); + } + } else { + authorizable.remove(); + log.debug("removing authorizable '{}' that no longer exists on IDP {}", id, idp.getName()); + } timer.mark("remove"); status = SyncResult.Status.DELETE; + } else { status = SyncResult.Status.MISSING; log.info("external identity missing for {}, but purge == false.", id); @@ -469,6 +482,14 @@ private void syncExternalIdentity(@Nonnull ExternalIdentity external, @Nonnull DefaultSyncConfig.Authorizable config) throws RepositoryException { syncProperties(external, authorizable, config.getPropertyMapping()); applyMembership(authorizable, config.getAutoMembership()); + + if (authorizable instanceof User) { + User user = (User) authorizable; + // ensure users are enabled or re-enabled + if (user.isDisabled()) { + user.disable(null); + } + } } /** diff --git a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfigImpl.java b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfigImpl.java index c1e840f..fdb30f3 100644 --- a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfigImpl.java +++ b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DefaultSyncConfigImpl.java @@ -178,6 +178,22 @@ public static final String PARAM_USER_DYNAMIC_MEMBERSHIP = "user.dynamicMembership"; /** + * @see User#getDisableMissing() + */ + public static final boolean PARAM_DISABLE_MISSING_USERS_DEFAULT = false; + + /** + * @see User#getDisableMissing() + */ + @Property( + label = "Disable missing users", + description = "If true, users that no longer exist on the external provider will be locally disabled, " + + "and re-enabled if they become valid again. If false (default) they will be removed.", + boolValue = false + ) + public static final String PARAM_DISABLE_MISSING_USERS = "user.disableMissing"; + + /** * @see DefaultSyncConfig.Group#getExpirationTime() */ public static final String PARAM_GROUP_EXPIRATION_TIME_DEFAULT = "1d"; @@ -268,6 +284,7 @@ public static DefaultSyncConfig of(ConfigurationParameters params) { .setName(params.getConfigValue(PARAM_NAME, PARAM_NAME_DEFAULT)); cfg.user() + .setDisableMissing(params.getConfigValue(PARAM_DISABLE_MISSING_USERS, PARAM_DISABLE_MISSING_USERS_DEFAULT)) .setMembershipExpirationTime(getMilliSeconds(params, PARAM_USER_MEMBERSHIP_EXPIRATION_TIME, PARAM_USER_MEMBERSHIP_EXPIRATION_TIME_DEFAULT, ONE_HOUR)) .setMembershipNestingDepth(params.getConfigValue(PARAM_USER_MEMBERSHIP_NESTING_DEPTH, PARAM_USER_MEMBERSHIP_NESTING_DEPTH_DEFAULT)) .setDynamicMembership(params.getConfigValue(PARAM_USER_DYNAMIC_MEMBERSHIP, PARAM_USER_DYNAMIC_MEMBERSHIP_DEFAULT)) diff --git a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java index 9df96a0..6c8eb9c 100644 --- a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java +++ b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/TestIdentityProvider.java @@ -81,7 +81,7 @@ public TestIdentityProvider(@Nonnull String idpName) { .withGroups("_gr_u_", "g%r%")); } - private void addUser(TestIdentity user) { + public void addUser(TestIdentity user) { externalUsers.put(user.getId().toLowerCase(), (TestUser) user); } diff --git a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContextTest.java b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContextTest.java index a09a98a..a316806 100644 --- a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContextTest.java +++ b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/basic/DefaultSyncContextTest.java @@ -386,6 +386,39 @@ public void testSyncRemovedUserById() throws Exception { } @Test + public void testSyncDisabledUserById() throws Exception { + // configure to disable missing users + syncConfig.user().setDisableMissing(true); + + // mark a regular repo user as external user from the test IDP + User u = userManager.createUser("test" + UUID.randomUUID(), null); + String userId = u.getID(); + setExternalID(u, idp.getName()); + + // test sync with 'keepmissing' = true + syncCtx.setKeepMissing(true); + SyncResult result = syncCtx.sync(userId); + assertEquals(SyncResult.Status.MISSING, result.getStatus()); + assertNotNull(userManager.getAuthorizable(userId)); + + // test sync with 'keepmissing' = false + syncCtx.setKeepMissing(false); + result = syncCtx.sync(userId); + assertEquals(SyncResult.Status.DELETE, result.getStatus()); + + Authorizable authorizable = userManager.getAuthorizable(userId); + assertNotNull(authorizable); + assertTrue(((User)authorizable).isDisabled()); + + // add user back + ((TestIdentityProvider)idp).addUser(new TestIdentityProvider.TestUser(userId, idp.getName())); + + result = syncCtx.sync(userId); + assertEquals(SyncResult.Status.UPDATE, result.getStatus()); + assertNotNull(userManager.getAuthorizable(userId)); + } + + @Test public void testSyncGroupById() throws Exception { ExternalIdentity externalId = idp.listGroups().next();