Merge patch branch changes to main.

This commit is contained in:
Virtually Nick
2024-12-02 18:54:33 -05:00
17 changed files with 463 additions and 276 deletions

View File

@@ -0,0 +1,170 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.environment;
import org.apache.guacamole.properties.GuacamoleProperties;
import java.util.Collection;
import java.util.Collections;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleUnsupportedException;
import org.apache.guacamole.properties.CaseSensitivity;
import org.apache.guacamole.properties.GuacamoleProperty;
import org.apache.guacamole.properties.StringGuacamoleProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Internal implementation of Environment that provides the default
* implementations for any functions that are added to the Environment
* interface. This is primarily necessary to allow those default implementations
* to log warnings or informational messages without needing to repeatedly
* recreate the Logger.
*/
class DefaultEnvironment extends DelegatingEnvironment {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(DefaultEnvironment.class);
/**
* Creates a new DefaultEnvironment that provides default implementations
* for functions that may not be implemented by the given Environment
* implementation. The versions provided by DefaultEnvironment must still
* be manually called by actual <code>public default</code> functions on
* Environment to have any effect.
*
* @param environment
* The environment that may not provide implementations for all
* functions defined by the Environment interface.
*/
protected DefaultEnvironment(Environment environment) {
super(environment);
}
@Override
public <Type> Collection<Type> getPropertyCollection(
GuacamoleProperty<Type> property) throws GuacamoleException {
/* Pull the given property as a string. */
StringGuacamoleProperty stringProperty = new StringGuacamoleProperty() {
@Override
public String getName() { return property.getName(); }
};
/* Parse the string to a Collection of the desired type. */
return property.parseValueCollection(getProperty(stringProperty));
}
@Override
public <Type> Collection<Type> getPropertyCollection(
GuacamoleProperty<Type> property, Type defaultValue)
throws GuacamoleException {
/* Pull the given property as a string. */
StringGuacamoleProperty stringProperty = new StringGuacamoleProperty() {
@Override
public String getName() { return property.getName(); }
};
/* Check the value and return the default if null. */
String stringValue = getProperty(stringProperty);
if (stringValue == null)
return Collections.singletonList(defaultValue);
/* Parse the string and return the collection. */
return property.parseValueCollection(stringValue);
}
@Override
public <Type> Collection<Type> getPropertyCollection(
GuacamoleProperty<Type> property, Collection<Type> defaultValue)
throws GuacamoleException {
/* Pull the given property as a string. */
StringGuacamoleProperty stringProperty = new StringGuacamoleProperty() {
@Override
public String getName() { return property.getName(); }
};
/* Check the value and return the default if null. */
String stringValue = getProperty(stringProperty);
if (stringValue == null)
return defaultValue;
/* Parse the string and return the collection. */
return property.parseValueCollection(stringValue);
}
@Override
public <Type> Collection<Type> getRequiredPropertyCollection(
GuacamoleProperty<Type> property) throws GuacamoleException {
/* Pull the given property as a string. */
StringGuacamoleProperty stringProperty = new StringGuacamoleProperty() {
@Override
public String getName() { return property.getName(); }
};
/* Parse the string to a Collection of the desired type. */
return property.parseValueCollection(getRequiredProperty(stringProperty));
}
@Override
public void addGuacamoleProperties(GuacamoleProperties properties)
throws GuacamoleException {
throw new GuacamoleUnsupportedException(String.format("%s does not "
+ "support dynamic definition of Guacamole properties.",
getClass()));
}
@Override
public CaseSensitivity getCaseSensitivity() {
try {
return DefaultEnvironment.this.getProperty(CASE_SENSITIVITY, CaseSensitivity.ENABLED);
}
catch (GuacamoleException e) {
logger.error("Defaulting to case-sensitive handling of "
+ "usernames and group names as the desired case "
+ "sensitivity configuration could not be read: {}",
e.getMessage());
logger.debug("Error reading case sensitivity configuration.", e);
return CaseSensitivity.ENABLED;
}
}
}

View File

@@ -116,7 +116,7 @@ public class DelegatingEnvironment implements Environment {
}
@Override
public CaseSensitivity getCaseSensitivity() throws GuacamoleException {
public CaseSensitivity getCaseSensitivity() {
return environment.getCaseSensitivity();
}

View File

@@ -24,6 +24,7 @@ import java.io.File;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.function.Supplier;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleUnsupportedException;
import org.apache.guacamole.net.auth.GuacamoleProxyConfiguration;
@@ -34,6 +35,8 @@ import org.apache.guacamole.properties.GuacamoleProperty;
import org.apache.guacamole.properties.IntegerGuacamoleProperty;
import org.apache.guacamole.properties.StringGuacamoleProperty;
import org.apache.guacamole.protocols.ProtocolInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The environment of an arbitrary Guacamole instance, describing available
@@ -82,7 +85,7 @@ public interface Environment {
@Override
public String getName() { return "case-sensitivity"; }
};
/**
@@ -183,18 +186,7 @@ public interface Environment {
*/
public default <Type> Collection<Type> getPropertyCollection(
GuacamoleProperty<Type> property) throws GuacamoleException {
/* Pull the given property as a string. */
StringGuacamoleProperty stringProperty = new StringGuacamoleProperty() {
@Override
public String getName() { return property.getName(); }
};
/* Parse the string to a Collection of the desired type. */
return property.parseValueCollection(getProperty(stringProperty));
return new DefaultEnvironment(this).getPropertyCollection(property);
}
/**
@@ -227,23 +219,7 @@ public interface Environment {
public default <Type> Collection<Type> getPropertyCollection(
GuacamoleProperty<Type> property, Type defaultValue)
throws GuacamoleException {
/* Pull the given property as a string. */
StringGuacamoleProperty stringProperty = new StringGuacamoleProperty() {
@Override
public String getName() { return property.getName(); }
};
/* Check the value and return the default if null. */
String stringValue = getProperty(stringProperty);
if (stringValue == null)
return Collections.singletonList(defaultValue);
/* Parse the string and return the collection. */
return property.parseValueCollection(stringValue);
return new DefaultEnvironment(this).getPropertyCollection(property, defaultValue);
}
/**
@@ -276,23 +252,7 @@ public interface Environment {
public default <Type> Collection<Type> getPropertyCollection(
GuacamoleProperty<Type> property, Collection<Type> defaultValue)
throws GuacamoleException {
/* Pull the given property as a string. */
StringGuacamoleProperty stringProperty = new StringGuacamoleProperty() {
@Override
public String getName() { return property.getName(); }
};
/* Check the value and return the default if null. */
String stringValue = getProperty(stringProperty);
if (stringValue == null)
return defaultValue;
/* Parse the string and return the collection. */
return property.parseValueCollection(stringValue);
return new DefaultEnvironment(this).getPropertyCollection(property, defaultValue);
}
/**
@@ -335,18 +295,7 @@ public interface Environment {
*/
public default <Type> Collection<Type> getRequiredPropertyCollection(
GuacamoleProperty<Type> property) throws GuacamoleException {
/* Pull the given property as a string. */
StringGuacamoleProperty stringProperty = new StringGuacamoleProperty() {
@Override
public String getName() { return property.getName(); }
};
/* Parse the string to a Collection of the desired type. */
return property.parseValueCollection(getRequiredProperty(stringProperty));
return new DefaultEnvironment(this).getRequiredPropertyCollection(property);
}
/**
@@ -378,11 +327,9 @@ public interface Environment {
*/
public default void addGuacamoleProperties(GuacamoleProperties properties)
throws GuacamoleException {
throw new GuacamoleUnsupportedException(String.format("%s does not "
+ "support dynamic definition of Guacamole properties.",
getClass()));
new DefaultEnvironment(this).addGuacamoleProperties(properties);
}
/**
* Returns the case sensitivity configuration for Guacamole as defined
* in guacamole.properties, or the default of enabling case sensitivity
@@ -391,12 +338,9 @@ public interface Environment {
* @return
* The case sensitivity setting as configured in guacamole.properties,
* or the default of enabling case sensitivity.
*
* @throws GuacamoleException
* If guacamole.properties cannot be read or parsed.
*/
public default CaseSensitivity getCaseSensitivity() throws GuacamoleException {
return getProperty(CASE_SENSITIVITY, CaseSensitivity.ENABLED);
public default CaseSensitivity getCaseSensitivity() {
return new DefaultEnvironment(this).getCaseSensitivity();
}
}

View File

@@ -21,11 +21,8 @@ package org.apache.guacamole.net.auth;
import java.util.Collections;
import java.util.Set;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.environment.LocalEnvironment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Basic implementation of an AuthenticatedUser which uses the username to
@@ -35,16 +32,42 @@ public abstract class AbstractAuthenticatedUser extends AbstractIdentifiable
implements AuthenticatedUser {
/**
* The logger for this class.
* Creates a new AbstractAuthenticatedUser that considers usernames to be
* case-sensitive or case-insensitive based on the provided case sensitivity
* flag.
*
* @param caseSensitive
* true if usernames should be considered case-sensitive, false
* otherwise.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAuthenticatedUser.class);
public AbstractAuthenticatedUser(boolean caseSensitive) {
super(caseSensitive);
}
/**
* The server environment in which this Guacamole Client instance is
* running.
* Creates a new AbstractAuthenticatedUser that considers usernames to be
* case-sensitive or case-insensitive based on the case sensitivity setting
* of the provided {@link Environment}, as returned by
* {@link Environment#getCaseSensitivity()}.
*
* @param environment
* The Environment that should determine whether this
* AbstractAuthenticatedUser considers usernames to be case-sensitive.
*/
private final Environment environment = LocalEnvironment.getInstance();
public AbstractAuthenticatedUser(Environment environment) {
this(environment.getCaseSensitivity().caseSensitiveUsernames());
}
/**
* Creates a new AbstractAuthenticatedUser that considers usernames to be
* case-sensitive or case-insensitive based on the case sensitivity setting
* of an instance of {@link LocalEnvironment}, as returned by
* {@link LocalEnvironment#getCaseSensitivity()}.
*/
public AbstractAuthenticatedUser() {
this(LocalEnvironment.getInstance());
}
// Prior functionality now resides within AbstractIdentifiable
@Override
@@ -52,21 +75,6 @@ public abstract class AbstractAuthenticatedUser extends AbstractIdentifiable
return Collections.<String>emptySet();
}
@Override
public boolean isCaseSensitive() {
try {
return environment.getCaseSensitivity().caseSensitiveUsernames();
}
catch (GuacamoleException e) {
LOGGER.error("Failed to retrieve the configuration for case sensitivity: {}. "
+ "Username comparisons will be case-sensitive.",
e.getMessage());
LOGGER.debug("An exception was caught when attempting to retrieve the "
+ "case sensitivity configuration.", e);
return true;
}
}
@Override
public void invalidate() {
// Nothing to invalidate

View File

@@ -19,10 +19,17 @@
package org.apache.guacamole.net.auth;
import org.apache.guacamole.properties.CaseSensitivity;
/**
* Abstract implementation of Identifiable which provides equals() and
* hashCode() implementations which use the identifier to determine equality.
* The identifier comparison is case-sensitive.
* The identifier comparison is case-sensitive unless configured otherwise via
* the {@link AbstractIdentifiable#AbstractIdentifiable(boolean)} constructor.
*
* If using case-insensitive identifiers, any identifiers that are retrieved
* from or assigned to this object will first be canonicalized to a
* case-insensitive form using {@link CaseSensitivity#canonicalize(java.lang.String, boolean)}.
*/
public abstract class AbstractIdentifiable implements Identifiable {
@@ -31,32 +38,58 @@ public abstract class AbstractIdentifiable implements Identifiable {
*/
private String identifier;
/**
* Whether this object's identifier should be compared in a case-sensitive
* manner. If NOT case-sensitive, the identifier will be transformed into a
* canonical, case-insensitive form before use, including during assignment
* and retrieval. This affects the behavior of getIdentifier() and
* setIdentifier().
*/
private final boolean caseSensitive;
/**
* Creates a new AbstractIdentifiable that compares identifiers according
* to the provided case sensitivity flag. If using case-insensitive
* identifiers, any identifiers that are retrieved from or assigned to this
* object will first be canonicalized to a case-insensitive form using
* {@link CaseSensitivity#canonicalize(java.lang.String, boolean)}.
*
* @param caseSensitive
* true if identifiers should be compared in a case-sensitive manner,
* false otherwise.
*/
public AbstractIdentifiable(boolean caseSensitive) {
this.caseSensitive = caseSensitive;
}
/**
* Creates a new AbstractIdentifiable that compares identifiers in a
* case-sensitive manner. This is equivalent to invoking {@link #AbstractIdentifiable(boolean)}
* with the case sensitivity flag set to true.
*/
public AbstractIdentifiable() {
this(true);
}
@Override
public String getIdentifier() {
if (identifier == null || isCaseSensitive())
return identifier;
return identifier.toLowerCase();
return CaseSensitivity.canonicalize(identifier, caseSensitive);
}
@Override
public void setIdentifier(String identifier) {
if (isCaseSensitive() || identifier == null)
this.identifier = identifier;
else
this.identifier = identifier.toLowerCase();
this.identifier = CaseSensitivity.canonicalize(identifier, caseSensitive);
}
@Override
public int hashCode() {
if (identifier == null)
String thisIdentifier = getIdentifier();
if (thisIdentifier == null)
return 0;
if (isCaseSensitive())
return identifier.hashCode();
return identifier.toLowerCase().hashCode();
return thisIdentifier.hashCode();
}
@Override
@@ -66,20 +99,16 @@ public abstract class AbstractIdentifiable implements Identifiable {
if (other == null || getClass() != other.getClass())
return false;
// Get identifier of other object
// Get identifiers of objects being compared
String thisIdentifier = getIdentifier();
String otherIdentifier = ((AbstractIdentifiable) other).getIdentifier();
// If null, equal only if this identifier is null
if (otherIdentifier == null)
return identifier == null;
return thisIdentifier == null;
// If either this identifier or the one we're comparing to is
// case-sensitive, evaluate with case sensitivity.
if (isCaseSensitive() || ((AbstractIdentifiable) other).isCaseSensitive())
return otherIdentifier.equals(identifier);
// Both identifiers can be evaluated in a case-insensitive manner.
return otherIdentifier.equalsIgnoreCase(identifier);
// Otherwise, equal only if strings are identical
return otherIdentifier.equals(thisIdentifier);
}

View File

@@ -23,6 +23,8 @@ import java.util.Collections;
import java.util.Date;
import java.util.Map;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.environment.LocalEnvironment;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
import org.apache.guacamole.net.auth.permission.SystemPermissionSet;
@@ -40,6 +42,40 @@ public abstract class AbstractUser extends AbstractIdentifiable
*/
private String password;
/**
* Creates a new AbstractUser that considers usernames to be case-sensitive
* or case-insensitive based on the provided case sensitivity flag.
*
* @param caseSensitive
* true if usernames should be considered case-sensitive, false
* otherwise.
*/
public AbstractUser(boolean caseSensitive) {
super(caseSensitive);
}
/**
* Creates a new AbstractUser that considers usernames to be case-sensitive
* or case-insensitive based on the case sensitivity setting of the provided
* {@link Environment}, as returned by {@link Environment#getCaseSensitivity()}.
*
* @param environment
* The Environment that should determine whether this AbstractUser
* considers usernames to be case-sensitive.
*/
public AbstractUser(Environment environment) {
this(environment.getCaseSensitivity().caseSensitiveUsernames());
}
/**
* Creates a new AbstractUser that considers usernames to be case-sensitive
* or case-insensitive based on the case sensitivity setting of an instance
* of {@link LocalEnvironment}, as returned by @link LocalEnvironment#getCaseSensitivity()}.
*/
public AbstractUser() {
this(LocalEnvironment.getInstance());
}
@Override
public String getPassword() {
return password;
@@ -50,14 +86,6 @@ public abstract class AbstractUser extends AbstractIdentifiable
this.password = password;
}
@Override
public boolean isCaseSensitive() {
// In order to avoid causing incompatibility with other extensions,
// this class maintains case-sensitive comparisons.
return true;
}
/**
* {@inheritDoc}
*

View File

@@ -26,8 +26,6 @@ import org.apache.guacamole.environment.Environment;
import org.apache.guacamole.environment.LocalEnvironment;
import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
import org.apache.guacamole.net.auth.permission.SystemPermissionSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base implementation of UserGroup which provides default implementations of
@@ -36,16 +34,42 @@ import org.slf4j.LoggerFactory;
public abstract class AbstractUserGroup extends AbstractIdentifiable implements UserGroup {
/**
* The logger for this class.
* Creates a new AbstractUserGroup that considers group names to be
* case-sensitive or case-insensitive based on the provided case
* sensitivity flag.
*
* @param caseSensitive
* true if group names should be considered case-sensitive, false
* otherwise.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractUserGroup.class);
public AbstractUserGroup(boolean caseSensitive) {
super(caseSensitive);
}
/**
* The server environment in which this Guacamole Client instance is
* running.
* Creates a new AbstractUserGroup that considers group names to be
* case-sensitive or case-insensitive based on the case sensitivity setting
* of the provided {@link Environment}, as returned by
* {@link Environment#getCaseSensitivity()}.
*
* @param environment
* The Environment that should determine whether this AbstractUserGroup
* considers group names to be case-sensitive.
*/
private final Environment environment = LocalEnvironment.getInstance();
public AbstractUserGroup(Environment environment) {
this(environment.getCaseSensitivity().caseSensitiveGroupNames());
}
/**
* Creates a new AbstractUserGroup that considers group names to be
* case-sensitive or case-insensitive based on the case sensitivity setting
* of an instance of {@link LocalEnvironment}, as returned by
* {@link LocalEnvironment#getCaseSensitivity()}.
*/
public AbstractUserGroup() {
this(LocalEnvironment.getInstance());
}
/**
* {@inheritDoc}
*
@@ -194,20 +218,5 @@ public abstract class AbstractUserGroup extends AbstractIdentifiable implements
public RelatedObjectSet getMemberUserGroups() throws GuacamoleException {
return RelatedObjectSet.EMPTY_SET;
}
@Override
public boolean isCaseSensitive() {
try {
return environment.getCaseSensitivity().caseSensitiveGroupNames();
}
catch (GuacamoleException e) {
LOGGER.warn("Unable to retrieve server configuration, group names "
+ "will default to case-sensitive.");
LOGGER.debug("Received an exception attempting to retrieve the "
+ "property for group name case sensitivity, group names"
+ "will be treated as case-sensitive.", e);
return true;
}
}
}

View File

@@ -83,11 +83,6 @@ public class DelegatingUser implements User {
return user.isDisabled();
}
@Override
public boolean isCaseSensitive() {
return user.isCaseSensitive();
}
@Override
public void setDisabled(boolean disabled) {
user.setDisabled(disabled);

View File

@@ -43,18 +43,5 @@ public interface Identifiable {
* The identifier to assign.
*/
public void setIdentifier(String identifier);
/**
* Whether or not this identifier should be evaluated in a case-sensitive
* manner. By default this returns true and the identifier will be
* evaluated in a case-sensitive manner.
*
* @return
* True if the comparisons of this identifier should be case-sensitive,
* otherwise false.
*/
default public boolean isCaseSensitive() {
return true;
}
}

View File

@@ -19,6 +19,7 @@
package org.apache.guacamole.properties;
import java.util.Locale;
import org.apache.guacamole.properties.EnumGuacamoleProperty.PropertyValue;
/**
@@ -60,7 +61,19 @@ public enum CaseSensitivity {
* Whether or not case sensitivity should be enabled for group names.
*/
private final boolean groupNames;
/**
* Creates a new CaseSensitivity value that represents a setting controlling
* whether usernames and group names are case-sensitive.
*
* @param usernames
* true if usernames should be considered case-sensitive, false if
* usernames should be considered case-insensitive.
*
* @param groupNames
* true if group names should be considered case-sensitive, false if
* group names should be considered case-insensitive.
*/
CaseSensitivity(boolean usernames, boolean groupNames) {
this.usernames = usernames;
this.groupNames = groupNames;
@@ -88,5 +101,68 @@ public enum CaseSensitivity {
public boolean caseSensitiveGroupNames() {
return groupNames;
}
/**
* Converts the given identifier into a canonical form which ensures simple
* verbatim string comparisons are consistent with the case sensitivity
* setting. If case-sensitive, identifiers are simply returned without
* modification. If case-insensitive, identifiers are converted to
* lowercase with respect to the {@link Locale#ROOT} locale.
*
* @param identifier
* The identifier to convert into a canonical form.
*
* @param caseSensitive
* Whether the given identifier is case-sensitive.
*
* @return
* The given identifier, transformed as necessary to ensure that
* verbatim string comparisons are consistent with the case sensitivity
* setting.
*/
public static String canonicalize(String identifier, boolean caseSensitive) {
if (identifier == null)
return null;
return caseSensitive ? identifier : identifier.toLowerCase(Locale.ROOT);
}
/**
* Canonicalizes the given username according to whether usernames are
* case-sensitive under this case sensitivity setting. This function is
* equivalent to manually invoking {@link #canonicalize(java.lang.String, boolean)}
* with the value of {@link #caseSensitiveUsernames()}.
*
* @param username
* The username to canonicalize.
*
* @return
* The given username, transformed as necessary to ensure that
* verbatim string comparisons are consistent with the case sensitivity
* setting for usernames.
*/
public String canonicalizeUsername(String username) {
return canonicalize(username, usernames);
}
/**
* Canonicalizes the given group name according to whether group names are
* case-sensitive under this case sensitivity setting. This function is
* equivalent to manually invoking {@link #canonicalize(java.lang.String, boolean)}
* with the value of {@link #caseSensitiveGroupNames()}.
*
* @param groupName
* The group name to canonicalize.
*
* @return
* The given group name, transformed as necessary to ensure that
* verbatim string comparisons are consistent with the case sensitivity
* setting for group names.
*/
public String canonicalizeGroupName(String groupName) {
return canonicalize(groupName, groupNames);
}
}