From 27bbd35a3d8d7bfb41ac50dfc145e1314121d289 Mon Sep 17 00:00:00 2001 From: Josna battula Date: Wed, 15 Nov 2023 17:10:28 +1300 Subject: [PATCH 1/4] GUACAMOLE-1881: Adding new standard token LDAP_DOMAIN by extracting from user credentials --- .../ldap/AuthenticationProviderService.java | 69 +++++++++++++++---- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java index 654b403c34..f24024435c 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java @@ -28,6 +28,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.directory.api.ldap.model.entry.Attribute; import org.apache.directory.api.ldap.model.entry.Entry; import org.apache.directory.api.ldap.model.exception.LdapException; @@ -65,6 +67,11 @@ public class AuthenticationProviderService { */ public static final String LDAP_ATTRIBUTE_TOKEN_PREFIX = "LDAP_"; + /** + * The name of LDAP domain attribute. + */ + public static final String LDAP_DOMAIN_TOKEN = "DOMAIN"; + /** * Service for creating and managing connections to LDAP servers. */ @@ -286,18 +293,18 @@ public LDAPAuthenticatedUser authenticateUser(Credentials credentials) CredentialsInfo.USERNAME_PASSWORD); try { - + // Retrieve group membership of the user that just authenticated Set effectiveGroups = userGroupService.getParentUserGroupIdentifiers(config, config.getBindDN()); // Return AuthenticatedUser if bind succeeds LDAPAuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); + authenticatedUser.init(config, credentials, - getAttributeTokens(config), effectiveGroups); + getUserTokens(config, credentials), effectiveGroups); return authenticatedUser; - } catch (GuacamoleException | RuntimeException | Error e) { @@ -308,25 +315,53 @@ public LDAPAuthenticatedUser authenticateUser(Credentials credentials) } /** - * Returns parameter tokens generated from LDAP attributes on the user - * currently bound under the given LDAP connection. The attributes to be - * converted into parameter tokens must be explicitly listed in - * guacamole.properties. If no attributes are specified or none are - * found on the LDAP user object, an empty map is returned. + * Returns the current LDAP domain token from the provided user credentials. + * + * @param credentials + * The credentials used for authentication. + * + * @return + * Domain name by splitting login username or null if no domain is detected. + */ + private String getDomainToken(Credentials credentials) { + String username = credentials.getUsername(); + //Regex is used to extract the domain from a username + //that is in either of these formats: DOMAIN\\username or username@domain. + Pattern pattern = Pattern.compile("^(.+)\\\\.*$|^.*@(.+)$"); + Matcher matcher = pattern.matcher(username); + if (matcher.find()) { + return matcher.group(1) != null ? matcher.group(1) : matcher.group(2); + } + return null; + } + + /** + * Returns parameter tokens generated based on details specific to the user + * currently bound under the given LDAP connection. Both LDAP attributes on + * the user's associated LDAP object and the credentials submitted by the user + * to Guacamole are considered. If any tokens are to be derived from LDAP + * attributes, those attributes must be explicitly listed in + * guacamole.properties. If no tokens are applicable, an empty map is returned. * * @param config * The configuration of the LDAP server being queried. * + * @param credentials + * The credentials to use for authentication. + * * @return - * A map of parameter tokens generated from attributes on the user - * currently bound under the given LDAP connection, as a map of token - * name to corresponding value, or an empty map if no attributes are - * specified or none are found on the user object. + * A map of parameter tokens. These tokens are generated based on + * the attributes of the user currently bound under the given LDAP connection + * and the user's credentials. The map's keys are the canonicalized attribute + * names with an "LDAP_" prefix, and the values are the corresponding attribute + * values. If the domain name is extracted from the user's credentials, it is added + * to the map with the key "LDAP_DOMAIN". If no applicable tokens are found, + * the method returns an empty map. * * @throws GuacamoleException * If an error occurs retrieving the user DN or the attributes. */ - private Map getAttributeTokens(ConnectedLDAPConfiguration config) + private Map getUserTokens(ConnectedLDAPConfiguration config, Credentials credentials) throws GuacamoleException { // Get attributes from configuration information @@ -356,12 +391,18 @@ private Map getAttributeTokens(ConnectedLDAPConfiguration config tokens.put(TokenName.canonicalize(attr.getId(), LDAP_ATTRIBUTE_TOKEN_PREFIX), attr.getString()); } - } catch (LdapException e) { throw new GuacamoleServerException("Could not query LDAP user attributes.", e); } + // Extracting the domain name from the user's credentials + String domainName = getDomainToken(credentials); + if (domainName != null) { + String tokenName = TokenName.canonicalize(LDAP_DOMAIN_TOKEN, LDAP_ATTRIBUTE_TOKEN_PREFIX); + tokens.put(tokenName, domainName); + } + return tokens; } From 50fdb34540b0dd2fee10ff96210af5b5d7aee49d Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 15 May 2024 10:37:06 -0700 Subject: [PATCH 2/4] GUACAMOLE-1881: Remove unnecessary usage of TokenName.canonicalize() to derive static name. --- .../ldap/AuthenticationProviderService.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java index f24024435c..424e221f61 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java @@ -65,12 +65,13 @@ public class AuthenticationProviderService { /** * The prefix that will be used when generating tokens. */ - public static final String LDAP_ATTRIBUTE_TOKEN_PREFIX = "LDAP_"; + public static final String LDAP_TOKEN_PREFIX = "LDAP_"; /** - * The name of LDAP domain attribute. + * The name of parameter token that will contain the domain extracted from + * the LDAP user's username, if applicable. */ - public static final String LDAP_DOMAIN_TOKEN = "DOMAIN"; + public static final String LDAP_DOMAIN_TOKEN = LDAP_TOKEN_PREFIX + "DOMAIN"; /** * Service for creating and managing connections to LDAP servers. @@ -389,19 +390,18 @@ private Map getUserTokens(ConnectedLDAPConfiguration config, Cre // Convert each retrieved attribute into a corresponding token for (Attribute attr : attributes) { tokens.put(TokenName.canonicalize(attr.getId(), - LDAP_ATTRIBUTE_TOKEN_PREFIX), attr.getString()); + LDAP_TOKEN_PREFIX), attr.getString()); } } catch (LdapException e) { throw new GuacamoleServerException("Could not query LDAP user attributes.", e); } - // Extracting the domain name from the user's credentials + // Extract the domain (ie: Windows / Active Directory domain) from the + // user's credentials String domainName = getDomainToken(credentials); - if (domainName != null) { - String tokenName = TokenName.canonicalize(LDAP_DOMAIN_TOKEN, LDAP_ATTRIBUTE_TOKEN_PREFIX); - tokens.put(tokenName, domainName); - } + if (domainName != null) + tokens.put(LDAP_DOMAIN_TOKEN, domainName); return tokens; From b2b1452e119f71b0afe141064dcea4036db727cd Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 15 May 2024 10:38:20 -0700 Subject: [PATCH 3/4] GUACAMOLE-1881: Decouple domain extraction from whether the extracted domain is used for a token. --- .../auth/ldap/AuthenticationProviderService.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java index 424e221f61..8673f1ff31 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java @@ -316,15 +316,18 @@ public LDAPAuthenticatedUser authenticateUser(Credentials credentials) } /** - * Returns the current LDAP domain token from the provided user credentials. + * Returns the Windows / Active Directory domain included in the username + * of the provided user credentials. If the username does not contain a + * domain, null is returned. * * @param credentials * The credentials used for authentication. * * @return - * Domain name by splitting login username or null if no domain is detected. + * The domain name within the username of the provided credentials, or + * null if no domain is present. */ - private String getDomainToken(Credentials credentials) { + private String getUserDomain(Credentials credentials) { String username = credentials.getUsername(); //Regex is used to extract the domain from a username //that is in either of these formats: DOMAIN\\username or username@domain. @@ -399,7 +402,7 @@ private Map getUserTokens(ConnectedLDAPConfiguration config, Cre // Extract the domain (ie: Windows / Active Directory domain) from the // user's credentials - String domainName = getDomainToken(credentials); + String domainName = getUserDomain(credentials); if (domainName != null) tokens.put(LDAP_DOMAIN_TOKEN, domainName); From 37d8e5ada9ce50adefb2f65ed20082e0b86a0656 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 15 May 2024 10:40:29 -0700 Subject: [PATCH 4/4] GUACAMOLE-1881: Remove unnecessary recompilation of static domain extraction regex. --- .../ldap/AuthenticationProviderService.java | 48 +++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java index 8673f1ff31..66adb49e99 100644 --- a/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-ldap/src/main/java/org/apache/guacamole/auth/ldap/AuthenticationProviderService.java @@ -67,6 +67,29 @@ public class AuthenticationProviderService { */ public static final String LDAP_TOKEN_PREFIX = "LDAP_"; + /** + * Regular expression that extracts the Windows / Active Directory domain + * from a username in either of the following formats: down-level logon + * ("DOMAIN\\username") or UPN ("username@domain"). If the username is in + * "DOMAIN\\username" format, the domain will be stored in the first + * capturing group. If the username is in UPN format, the domain will be + * stored in the second capturing group. + */ + private static final Pattern LDAP_USERNAME_DOMAIN = Pattern.compile("^(.+)\\\\.*$|^.*@(.+)$"); + + /** + * The index of the capturing group in {@link #LDAP_USERNAME_DOMAIN} that + * captures the domain of a username in down-level logon format + * ("DOMAIN\\username"). + */ + private static final int LDAP_USERNAME_DOMAIN_DOWN_LEVEL_GROUP = 1; + + /** + * The index of the capturing group in {@link #LDAP_USERNAME_DOMAIN} that + * captures the domain of a username in UPN format ("username@domain"). + */ + private static final int LDAP_USERNAME_DOMAIN_UPN_GROUP = 2; + /** * The name of parameter token that will contain the domain extracted from * the LDAP user's username, if applicable. @@ -328,15 +351,24 @@ public LDAPAuthenticatedUser authenticateUser(Credentials credentials) * null if no domain is present. */ private String getUserDomain(Credentials credentials) { + + // Extract domain from username (not necessarily present) String username = credentials.getUsername(); - //Regex is used to extract the domain from a username - //that is in either of these formats: DOMAIN\\username or username@domain. - Pattern pattern = Pattern.compile("^(.+)\\\\.*$|^.*@(.+)$"); - Matcher matcher = pattern.matcher(username); - if (matcher.find()) { - return matcher.group(1) != null ? matcher.group(1) : matcher.group(2); - } - return null; + Matcher matcher = LDAP_USERNAME_DOMAIN.matcher(username); + if (!matcher.find()) + return null; + + String dlDomain = matcher.group(LDAP_USERNAME_DOMAIN_DOWN_LEVEL_GROUP); + String upnDomain = matcher.group(LDAP_USERNAME_DOMAIN_UPN_GROUP); + + // The two domain formats are mutually exclusive. If any format is + // present, the other must be absent unless there is a bug in the + // way the domains are parsed + assert(dlDomain == null || upnDomain == null); + + // Use whichever domain format is present + return dlDomain != null ? dlDomain : upnDomain; + } /**