OAK-10486 : Resolution of inherited groups may terminate pre-maturely for external users
diff --git a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/InheritedMembershipIterator.java b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/InheritedMembershipIterator.java
index 8b19f07..bc142cd 100644
--- a/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/InheritedMembershipIterator.java
+++ b/oak-auth-external/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/principal/InheritedMembershipIterator.java
@@ -54,6 +54,7 @@
try {
// call 'memberof' to cover nested inheritance
Iterator<Group> it = gr.memberOf();
+ // verify that the group-iterator has any elements before remembering it for further processing
if (it.hasNext()) {
inherited.add(it);
}
@@ -63,18 +64,11 @@
return gr;
}
- if (inheritedIterator == null) {
- inheritedIterator = getNextInheritedIterator();
- }
-
- while (inheritedIterator.hasNext()) {
+ while (inheritedHasNext()) {
Group gr = inheritedIterator.next();
if (notProcessedBefore(gr)) {
return gr;
}
- if (!inheritedIterator.hasNext()) {
- inheritedIterator = getNextInheritedIterator();
- }
}
// all inherited groups have been processed
@@ -88,6 +82,21 @@
return true;
}
}
+
+ private boolean inheritedHasNext() {
+ if (inheritedIterator == null) {
+ // initialize the inherited iterator (i.e. get the first one after having processed all dynamic groups)
+ inheritedIterator = getNextInheritedIterator();
+ }
+ if (inheritedIterator.hasNext()) {
+ return true;
+ } else {
+ // no more elements in the current 'inheritedIterator'. move on to the next inherited iterator
+ // (or an empty one if there are no more iterators left to process)
+ inheritedIterator = getNextInheritedIterator();
+ return inheritedIterator.hasNext();
+ }
+ }
@NotNull
private Iterator<Group> getNextInheritedIterator() {
diff --git a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncTest.java b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncTest.java
index c653741..1ac7309 100644
--- a/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncTest.java
+++ b/oak-auth-external/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/external/impl/DynamicSyncTest.java
@@ -50,6 +50,8 @@
private static final String BASE_ID = "base";
private static final String BASE2_ID = "base2";
+ private static final String BASE3_ID = "base3";
+ private static final String BASE4_ID = "base4";
private static final String AUTO_GROUPS = "autoForGroups";
private static final String AUTO_USERS = "autoForUsers";
@@ -57,6 +59,8 @@
private Group autoForUsers;
private Group base;
private Group base2;
+ private Group base3;
+ private Group base4;
@Override
public void before() throws Exception {
@@ -76,6 +80,9 @@
base2 = userManager.createGroup(BASE2_ID);
base2.addMember(autoForUsers);
+ base3 = userManager.createGroup(BASE3_ID);
+ base4 = userManager.createGroup(BASE4_ID);
+ base4.addMembers(BASE3_ID);
r.commit();
}
@@ -131,6 +138,65 @@
}
@Test
+ public void testSyncedUserDifferentBaseGroups() throws Exception {
+ // 'b' is member of local group 'base3' (not of 'base')
+ // 'a' is member of local group 'base'
+ assertTrue(base3.addMembers("b").isEmpty());
+ assertTrue(base.removeMembers("b").isEmpty());
+ root.commit();
+
+ ExternalUser externalUser = idp.getUser(USER_ID);
+ sync(externalUser, SyncResult.Status.ADD);
+
+ Authorizable user = userManager.getAuthorizable(USER_ID);
+ assertNotNull(user);
+
+ // assert membership
+ Set<String> expDeclaredGroupIds = ImmutableSet.of("a", "b", "c", "aa", "aaa", AUTO_GROUPS, AUTO_USERS, EveryonePrincipal.NAME);
+ assertExpectedIds(expDeclaredGroupIds, user.declaredMemberOf());
+
+ Set<String> expGroupIds = ImmutableSet.of(BASE_ID, BASE2_ID, BASE3_ID, BASE4_ID, "a", "b", "c", "aa", "aaa", AUTO_GROUPS, AUTO_USERS, EveryonePrincipal.NAME);
+ assertExpectedIds(expGroupIds, user.memberOf());
+
+ // assert groups
+ user.declaredMemberOf().forEachRemaining(group -> assertIsMember(group, true, user));
+ user.memberOf().forEachRemaining(group -> assertIsMember(group, false, user));
+
+ // assert principals
+ List<String> principalNames = getPrincipalNames(getPrincipalManager(r).getGroupMembership(user.getPrincipal()));
+ assertEquals(12, principalNames.size());
+ }
+
+ @Test
+ public void testSyncedUserDifferentBaseGroupsWithDuplication() throws Exception {
+ // 'b' is member of local group 'base3' and 'base'
+ // 'a' is member of local group 'base'
+ assertTrue(base3.addMembers("b").isEmpty());
+ root.commit();
+
+ ExternalUser externalUser = idp.getUser(USER_ID);
+ sync(externalUser, SyncResult.Status.ADD);
+
+ Authorizable user = userManager.getAuthorizable(USER_ID);
+ assertNotNull(user);
+
+ // assert membership
+ Set<String> expDeclaredGroupIds = ImmutableSet.of("a", "b", "c", "aa", "aaa", AUTO_GROUPS, AUTO_USERS, EveryonePrincipal.NAME);
+ assertExpectedIds(expDeclaredGroupIds, user.declaredMemberOf());
+
+ Set<String> expGroupIds = ImmutableSet.of(BASE_ID, BASE2_ID, BASE3_ID, BASE4_ID, "a", "b", "c", "aa", "aaa", AUTO_GROUPS, AUTO_USERS, EveryonePrincipal.NAME);
+ assertExpectedIds(expGroupIds, user.memberOf());
+
+ // assert groups
+ user.declaredMemberOf().forEachRemaining(group -> assertIsMember(group, true, user));
+ user.memberOf().forEachRemaining(group -> assertIsMember(group, false, user));
+
+ // assert principals
+ List<String> principalNames = getPrincipalNames(getPrincipalManager(r).getGroupMembership(user.getPrincipal()));
+ assertEquals(12, principalNames.size());
+ }
+
+ @Test
public void testSyncedGroup() throws Exception {
ExternalUser externalUser = idp.getUser(USER_ID);
sync(externalUser, SyncResult.Status.ADD);