keycloak-aplcache
Changes
federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java 2(+1 -1)
federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java 6(+6 -0)
federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupMapperConfig.java 7(+7 -0)
federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupTreeResolver.java 20(+15 -5)
federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/GroupTreeResolverTest.java 18(+12 -6)
Details
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java
index fcdae3c..0ae1627 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapper.java
@@ -175,7 +175,7 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements
// Now we have list of LDAP groups. Let's form the tree (if needed)
if (config.isPreserveGroupsInheritance()) {
try {
- List<GroupTreeResolver.GroupTreeEntry> groupTrees = new GroupTreeResolver().resolveGroupTree(ldapGroupsRep);
+ List<GroupTreeResolver.GroupTreeEntry> groupTrees = new GroupTreeResolver().resolveGroupTree(ldapGroupsRep, config.isIgnoreMissingGroups());
updateKeycloakGroupTree(realm, groupTrees, ldapGroupsMap, syncResult);
} catch (GroupTreeResolver.GroupTreeResolveException gre) {
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java
index b093bcb..14c5ef2 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupLDAPStorageMapperFactory.java
@@ -114,6 +114,12 @@ public class GroupLDAPStorageMapperFactory extends AbstractLDAPStorageMapperFact
.type(ProviderConfigProperty.BOOLEAN_TYPE)
.defaultValue("true")
.add()
+ .property().name(GroupMapperConfig.IGNORE_MISSING_GROUPS)
+ .label("Ignore Missing Groups")
+ .helpText("Ignore missing groups in the group hierarchy")
+ .type(ProviderConfigProperty.BOOLEAN_TYPE)
+ .defaultValue("false")
+ .add()
.property().name(GroupMapperConfig.MEMBERSHIP_LDAP_ATTRIBUTE)
.label("Membership LDAP Attribute")
.helpText("Name of LDAP attribute on group, which is used for membership mappings. Usually it will be 'member' ." +
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupMapperConfig.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupMapperConfig.java
index b305cb4..36a2fc6 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupMapperConfig.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupMapperConfig.java
@@ -44,6 +44,9 @@ public class GroupMapperConfig extends CommonLDAPGroupMapperConfig {
// Flag whether group inheritance from LDAP should be propagated to Keycloak group inheritance.
public static final String PRESERVE_GROUP_INHERITANCE = "preserve.group.inheritance";
+ // Flag whether missing groups should be ignored.
+ public static final String IGNORE_MISSING_GROUPS = "ignore.missing.groups";
+
// Customized LDAP filter which is added to the whole LDAP query
public static final String GROUPS_LDAP_FILTER = "groups.ldap.filter";
@@ -90,6 +93,10 @@ public class GroupMapperConfig extends CommonLDAPGroupMapperConfig {
return AbstractLDAPStorageMapper.parseBooleanParameter(mapperModel, PRESERVE_GROUP_INHERITANCE);
}
+ public boolean isIgnoreMissingGroups() {
+ return AbstractLDAPStorageMapper.parseBooleanParameter(mapperModel, IGNORE_MISSING_GROUPS);
+ }
+
public Collection<String> getGroupObjectClasses(LDAPStorageProvider ldapProvider) {
String objectClasses = mapperModel.getConfig().getFirst(GROUP_OBJECT_CLASSES);
if (objectClasses == null) {
diff --git a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupTreeResolver.java b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupTreeResolver.java
index d38f361..b28374e 100644
--- a/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupTreeResolver.java
+++ b/federation/ldap/src/main/java/org/keycloak/storage/ldap/mappers/membership/group/GroupTreeResolver.java
@@ -17,6 +17,8 @@
package org.keycloak.storage.ldap.mappers.membership.group;
+import org.jboss.logging.Logger;
+
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedList;
@@ -31,6 +33,8 @@ import java.util.TreeSet;
*/
public class GroupTreeResolver {
+ private static final Logger logger = Logger.getLogger(GroupTreeResolver.class);
+
/**
* Fully resolves list of group trees to be used in Keycloak. The input is group info (usually from LDAP) where each "Group" object contains
@@ -39,12 +43,13 @@ public class GroupTreeResolver {
* The operation also performs validation as rules for LDAP are less strict than for Keycloak (In LDAP, the recursion is possible and multiple parents of single group is also allowed)
*
* @param groups
+ * @param ignoreMissingGroups
* @return
* @throws GroupTreeResolveException
*/
- public List<GroupTreeEntry> resolveGroupTree(List<Group> groups) throws GroupTreeResolveException {
+ public List<GroupTreeEntry> resolveGroupTree(List<Group> groups, boolean ignoreMissingGroups) throws GroupTreeResolveException {
// 1- Get parents of each group
- Map<String, List<String>> parentsTree = getParentsTree(groups);
+ Map<String, List<String>> parentsTree = getParentsTree(groups, ignoreMissingGroups);
// 2 - Get rootGroups (groups without parent) and check if there is no group with multiple parents
List<String> rootGroups = new LinkedList<>();
@@ -96,7 +101,7 @@ public class GroupTreeResolver {
return finalResult;
}
- private Map<String, List<String>> getParentsTree(List<Group> groups) throws GroupTreeResolveException {
+ private Map<String, List<String>> getParentsTree(List<Group> groups, boolean ignoreMissingGroups) throws GroupTreeResolveException {
Map<String, List<String>> result = new TreeMap<>();
for (Group group : groups) {
@@ -106,10 +111,15 @@ public class GroupTreeResolver {
for (Group group : groups) {
for (String child : group.getChildrenNames()) {
List<String> list = result.get(child);
- if (list == null) {
+ if (list != null) {
+ list.add(group.getGroupName());
+ } else if(ignoreMissingGroups){
+ // Need to remove the missing group
+ group.getChildrenNames().remove(child);
+ logger.debug("Group '" + child + "' referenced as member of group '" + group.getGroupName() + "' doesn't exists. Ignoring.");
+ } else {
throw new GroupTreeResolveException("Group '" + child + "' referenced as member of group '" + group.getGroupName() + "' doesn't exists");
}
- list.add(group.getGroupName());
}
}
return result;
diff --git a/federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/GroupTreeResolverTest.java b/federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/GroupTreeResolverTest.java
index 78974a2..8200971 100644
--- a/federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/GroupTreeResolverTest.java
+++ b/federation/ldap/src/test/java/org/keycloak/storage/ldap/idm/model/GroupTreeResolverTest.java
@@ -41,7 +41,7 @@ public class GroupTreeResolverTest {
List<GroupTreeResolver.Group> groups = Arrays.asList(group1, group2, group3, group4, group5, group6, group7);
GroupTreeResolver resolver = new GroupTreeResolver();
- List<GroupTreeResolver.GroupTreeEntry> groupTree = resolver.resolveGroupTree(groups);
+ List<GroupTreeResolver.GroupTreeEntry> groupTree = resolver.resolveGroupTree(groups, false);
Assert.assertEquals(1, groupTree.size());
Assert.assertEquals("{ group1 -> [ { group2 -> [ { group4 -> [ ]}{ group5 -> [ ]} ]}{ group3 -> [ { group6 -> [ { group7 -> [ ]} ]} ]} ]}", groupTree.get(0).toString());
}
@@ -60,7 +60,7 @@ public class GroupTreeResolverTest {
List<GroupTreeResolver.Group> groups = Arrays.asList(group1, group2, group3, group4, group5, group6, group7, group8, group9);
GroupTreeResolver resolver = new GroupTreeResolver();
- List<GroupTreeResolver.GroupTreeEntry> groupTree = resolver.resolveGroupTree(groups);
+ List<GroupTreeResolver.GroupTreeEntry> groupTree = resolver.resolveGroupTree(groups, false);
Assert.assertEquals(2, groupTree.size());
Assert.assertEquals("{ group3 -> [ { group2 -> [ ]} ]}", groupTree.get(0).toString());
@@ -81,7 +81,7 @@ public class GroupTreeResolverTest {
GroupTreeResolver resolver = new GroupTreeResolver();
try {
- resolver.resolveGroupTree(groups);
+ resolver.resolveGroupTree(groups, false);
Assert.fail("Exception expected because of recursion");
} catch (GroupTreeResolver.GroupTreeResolveException gre) {
Assert.assertTrue(gre.getMessage().startsWith("Recursion detected"));
@@ -99,7 +99,7 @@ public class GroupTreeResolverTest {
GroupTreeResolver resolver = new GroupTreeResolver();
try {
- resolver.resolveGroupTree(groups);
+ resolver.resolveGroupTree(groups, false);
Assert.fail("Exception expected because of some groups have multiple parents");
} catch (GroupTreeResolver.GroupTreeResolveException gre) {
Assert.assertTrue(gre.getMessage().contains("detected to have multiple parents"));
@@ -108,7 +108,7 @@ public class GroupTreeResolverTest {
@Test
- public void testGroupResolvingMissingGroup() {
+ public void testGroupResolvingMissingGroup() throws GroupTreeResolver.GroupTreeResolveException {
GroupTreeResolver.Group group1 = new GroupTreeResolver.Group("group1", "group2");
GroupTreeResolver.Group group2 = new GroupTreeResolver.Group("group2", "group3");
GroupTreeResolver.Group group4 = new GroupTreeResolver.Group("group4");
@@ -116,10 +116,16 @@ public class GroupTreeResolverTest {
GroupTreeResolver resolver = new GroupTreeResolver();
try {
- resolver.resolveGroupTree(groups);
+ resolver.resolveGroupTree(groups, false);
Assert.fail("Exception expected because of missing referenced group");
} catch (GroupTreeResolver.GroupTreeResolveException gre) {
Assert.assertEquals("Group 'group3' referenced as member of group 'group2' doesn't exists", gre.getMessage());
}
+
+ List<GroupTreeResolver.GroupTreeEntry> groupTree = resolver.resolveGroupTree(groups, true);
+
+ Assert.assertEquals(2, groupTree.size());
+ Assert.assertEquals("{ group1 -> [ { group2 -> [ ]} ]}", groupTree.get(0).toString());
+ Assert.assertEquals("{ group4 -> [ ]}", groupTree.get(1).toString());
}
}
diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/SetPasswordCmd.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/SetPasswordCmd.java
index f3ce6ea..805a830 100644
--- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/SetPasswordCmd.java
+++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/SetPasswordCmd.java
@@ -138,7 +138,7 @@ public class SetPasswordCmd extends AbstractAuthOptionsCmd {
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
- out.println("Usage: " + CMD + " set-password (--username USERNAME | --userid ID) [--password PASSWORD] [ARGUMENTS]");
+ out.println("Usage: " + CMD + " set-password (--username USERNAME | --userid ID) [--new-password PASSWORD] [ARGUMENTS]");
out.println();
out.println("Command to reset user's password.");
out.println();
@@ -167,7 +167,7 @@ public class SetPasswordCmd extends AbstractAuthOptionsCmd {
out.println("Examples:");
out.println();
out.println("Set new temporary password for the user:");
- out.println(" " + PROMPT + " " + CMD + " set-password -r demorealm --username testuser --password NEWPASS -t");
+ out.println(" " + PROMPT + " " + CMD + " set-password -r demorealm --username testuser --new-password NEWPASS -t");
out.println();
out.println();
out.println("Use '" + CMD + " help' for general information and a list of commands");
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java
index fe2caa4..3b427f0 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java
@@ -123,7 +123,7 @@ public class KcAdmTest extends AbstractAdmCliTest {
exe = KcAdmExec.execute("set-password");
assertExitCodeAndStdErrSize(exe, 1, 0);
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
- Assert.assertEquals("help message", "Usage: " + CMD + " set-password (--username USERNAME | --userid ID) [--password PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0));
+ Assert.assertEquals("help message", "Usage: " + CMD + " set-password (--username USERNAME | --userid ID) [--new-password PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0));
//Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
exe = KcAdmExec.execute("help");
@@ -174,7 +174,7 @@ public class KcAdmTest extends AbstractAdmCliTest {
exe = KcAdmExec.execute("set-password --help");
assertExitCodeAndStdErrSize(exe, 0, 0);
- Assert.assertEquals("stdout first line", "Usage: " + CMD + " set-password (--username USERNAME | --userid ID) [--password PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0));
+ Assert.assertEquals("stdout first line", "Usage: " + CMD + " set-password (--username USERNAME | --userid ID) [--new-password PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0));
exe = KcAdmExec.execute("config --help");
assertExitCodeAndStdErrSize(exe, 0, 0);