GUACAMOLE-954: Add LDAP support for nested user groups

This commit is contained in:
Steven T. Barnhart committed 2025-10-30 16:37:43 -04:00
1 parent 82762fade3
commit 2148358250
7 files changed
+85 -3

No files matched your search

@@ -223,5 +223,10 @@ public class ConnectedLDAPConfiguration implements LDAPConfiguration, AutoClosea
public MemberAttributeType getMemberAttributeType() throws GuacamoleException { public MemberAttributeType getMemberAttributeType() throws GuacamoleException {
return config.getMemberAttributeType(); return config.getMemberAttributeType();
} }
@Override
public boolean getNestedGroups() throws GuacamoleException {
return config.getNestedGroups();
}
} }
@@ -151,4 +151,9 @@ public class DefaultLDAPConfiguration implements LDAPConfiguration {
return MemberAttributeType.DN; return MemberAttributeType.DN;
} }
@Override
public boolean getNestedGroups() {
return false;
}
} }
@@ -234,4 +234,12 @@ public class EnvironmentLDAPConfiguration implements LDAPConfiguration {
); );
} }
@Override
public boolean getNestedGroups() throws GuacamoleException {
return environment.getProperty(
LDAPGuacamoleProperties.LDAP_NESTED_GROUPS,
DEFAULT.getNestedGroups()
);
}
} }
@@ -204,6 +204,13 @@ public class JacksonLDAPConfiguration implements LDAPConfiguration {
@JsonProperty("member-attribute-type") @JsonProperty("member-attribute-type")
private String memberAttributeType; private String memberAttributeType;
/**
* The raw YAML value of {@link LDAPGuacamoleProperties#LDAP_NESTED_GROUPS}.
* If not set within the YAML, this will be false.
*/
@JsonProperty("nested-groups")
private Boolean nestedGroups;
/** /**
* The default configuration options for all parameters. * The default configuration options for all parameters.
*/ */
@@ -434,6 +441,11 @@ public class JacksonLDAPConfiguration implements LDAPConfiguration {
return withDefault(memberAttribute, defaultConfig::getMemberAttribute); return withDefault(memberAttribute, defaultConfig::getMemberAttribute);
} }
@Override
public boolean getNestedGroups() throws GuacamoleException {
return withDefault(nestedGroups, defaultConfig::getNestedGroups);
}
@Override @Override
public MemberAttributeType getMemberAttributeType() throws GuacamoleException { public MemberAttributeType getMemberAttributeType() throws GuacamoleException {
return withDefault(LDAPGuacamoleProperties.LDAP_MEMBER_ATTRIBUTE_TYPE, return withDefault(LDAPGuacamoleProperties.LDAP_MEMBER_ATTRIBUTE_TYPE,
@@ -334,4 +334,16 @@ public interface LDAPConfiguration {
*/ */
MemberAttributeType getMemberAttributeType() throws GuacamoleException; MemberAttributeType getMemberAttributeType() throws GuacamoleException;
/**
* Returns whether nested groups should be included in group membership.
*
* @return
* Whether to search in nested groups.
*
* @throws GuacamoleException
* If the configuration information determining whether nested
* groups should be used cannot be retrieved.
*/
boolean getNestedGroups() throws GuacamoleException;
} }
@@ -307,4 +307,15 @@ public class LDAPGuacamoleProperties {
}; };
/**
* Whether or not to search nested groups.
*/
public static final BooleanGuacamoleProperty LDAP_NESTED_GROUPS =
new BooleanGuacamoleProperty() {
@Override
public String getName() { return "ldap-nested-groups"; }
};
} }
@@ -27,10 +27,12 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.directory.api.ldap.model.entry.Entry; import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.entry.Value;
import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
import org.apache.directory.api.ldap.model.filter.AndNode; import org.apache.directory.api.ldap.model.filter.AndNode;
import org.apache.directory.api.ldap.model.filter.EqualityNode; import org.apache.directory.api.ldap.model.filter.EqualityNode;
import org.apache.directory.api.ldap.model.filter.ExprNode; import org.apache.directory.api.ldap.model.filter.ExprNode;
import org.apache.directory.api.ldap.model.filter.ExtensibleNode;
import org.apache.directory.api.ldap.model.filter.NotNode; import org.apache.directory.api.ldap.model.filter.NotNode;
import org.apache.directory.api.ldap.model.name.Dn; import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.guacamole.auth.ldap.conf.MemberAttributeType; import org.apache.guacamole.auth.ldap.conf.MemberAttributeType;
@@ -55,6 +57,11 @@ public class UserGroupService {
*/ */
private static final Logger logger = LoggerFactory.getLogger(UserGroupService.class); private static final Logger logger = LoggerFactory.getLogger(UserGroupService.class);
/**
* Constant for nested LDAP group matching in Active Directory
*/
private final String LDAP_GROUP_NESTED_MATCHING_OID = "1.2.840.113556.1.4.1941";
/** /**
* Service for executing LDAP queries. * Service for executing LDAP queries.
*/ */
@@ -227,14 +234,36 @@ public class UserGroupService {
groupAttributes.add(memberAttribute); groupAttributes.add(memberAttribute);
// Get all groups the user is a member of starting at the groupBaseDN, // Get all groups the user is a member of starting at the groupBaseDN,
// excluding guacConfigGroups // excluding guacConfigGroups and evaluating nested groups
// (if enabled).
ExprNode groupFilter = config.getGroupSearchFilter();
String filterValue = userIDorDN;
if (config.getNestedGroups()) {
// Add support for nested groups using LDAP_MATCHING_RULE_IN_CHAIN
// (memberOf:1.2.840.113556.1.4.1941:=<UserDN>)
// Matching rule OID for LDAP_MATCHING_RULE_IN_CHAIN
// ** This possibly only supports Active Directory **
ExtensibleNode node = new ExtensibleNode("member");
filterValue = null;
// Explicitly set the matching rule ID and dnAttributes
node.setMatchingRuleId(LDAP_GROUP_NESTED_MATCHING_OID);
node.setDnAttributes(false);
node.setValue(new Value(userIDorDN));
groupFilter = new AndNode(
groupFilter, node
);
}
return queryService.search( return queryService.search(
config, config,
config.getLDAPConnection(), config.getLDAPConnection(),
groupBaseDN, groupBaseDN,
getGroupSearchFilter(config), groupFilter,
Collections.singleton(memberAttribute), Collections.singleton(memberAttribute),
userIDorDN, filterValue,
groupAttributes groupAttributes
); );