mirror of
				https://github.com/gyurix1968/guacamole-client.git
				synced 2025-10-31 00:53:21 +00:00 
			
		
		
		
	Merge 1.3.0 changes back to master.
This commit is contained in:
		| @@ -20,9 +20,7 @@ | ||||
| package org.apache.guacamole.auth.cas; | ||||
|  | ||||
| import com.google.inject.Inject; | ||||
| import com.google.inject.Provider; | ||||
| import java.util.Arrays; | ||||
| import java.util.Map; | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| import org.apache.guacamole.form.Field; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| @@ -53,12 +51,6 @@ public class AuthenticationProviderService { | ||||
|     @Inject | ||||
|     private TicketValidationService ticketService; | ||||
|  | ||||
|     /** | ||||
|      * Provider for AuthenticatedUser objects. | ||||
|      */ | ||||
|     @Inject | ||||
|     private Provider<CASAuthenticatedUser> authenticatedUserProvider; | ||||
|  | ||||
|     /** | ||||
|      * Returns an AuthenticatedUser representing the user authenticated by the | ||||
|      * given credentials. | ||||
| @@ -82,13 +74,7 @@ public class AuthenticationProviderService { | ||||
|         if (request != null) { | ||||
|             String ticket = request.getParameter(CASTicketField.PARAMETER_NAME); | ||||
|             if (ticket != null) { | ||||
|                 Map<String, String> tokens = ticketService.validateTicket(ticket, credentials); | ||||
|                 String username = credentials.getUsername(); | ||||
|                 if (username != null) { | ||||
|                     CASAuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); | ||||
|                     authenticatedUser.init(username, credentials, tokens); | ||||
|                     return authenticatedUser; | ||||
|                 } | ||||
|                 return ticketService.validateTicket(ticket, credentials); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -19,7 +19,10 @@ | ||||
|  | ||||
| package org.apache.guacamole.auth.cas.conf; | ||||
|  | ||||
| import org.apache.guacamole.auth.cas.group.GroupFormat; | ||||
| import org.apache.guacamole.properties.EnumGuacamoleProperty; | ||||
| import org.apache.guacamole.properties.URIGuacamoleProperty; | ||||
| import org.apache.guacamole.properties.StringGuacamoleProperty; | ||||
|  | ||||
| /** | ||||
|  * Provides properties required for use of the CAS authentication provider. | ||||
| @@ -68,5 +71,51 @@ public class CASGuacamoleProperties { | ||||
|         public String getName() { return "cas-clearpass-key"; } | ||||
|  | ||||
|     }; | ||||
|    | ||||
|     /** | ||||
|      * The name of the CAS attribute used for group membership, such as | ||||
|      * "memberOf". This attribute is case sensitive. | ||||
|      */ | ||||
|     public static final StringGuacamoleProperty CAS_GROUP_ATTRIBUTE = | ||||
|             new StringGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "cas-group-attribute"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The format used by CAS to represent group names. Possible formats are | ||||
|      * "plain" (simple text names) or "ldap" (fully-qualified LDAP DNs). | ||||
|      */ | ||||
|     public static final EnumGuacamoleProperty<GroupFormat> CAS_GROUP_FORMAT = | ||||
|             new EnumGuacamoleProperty<GroupFormat>(GroupFormat.class) { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "cas-group-format"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The LDAP base DN to require for all CAS groups. | ||||
|      */ | ||||
|     public static final LdapNameGuacamoleProperty CAS_GROUP_LDAP_BASE_DN = | ||||
|             new LdapNameGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "cas-group-ldap-base-dn"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The LDAP attribute to require for the names of CAS groups. | ||||
|      */ | ||||
|     public static final StringGuacamoleProperty CAS_GROUP_LDAP_ATTRIBUTE = | ||||
|             new StringGuacamoleProperty() { | ||||
|  | ||||
|         @Override | ||||
|         public String getName() { return "cas-group-ldap-attribute"; } | ||||
|  | ||||
|     }; | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -22,8 +22,14 @@ package org.apache.guacamole.auth.cas.conf; | ||||
| import com.google.inject.Inject; | ||||
| import java.net.URI; | ||||
| import java.security.PrivateKey; | ||||
| import javax.naming.ldap.LdapName; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.GuacamoleServerException; | ||||
| import org.apache.guacamole.auth.cas.group.GroupFormat; | ||||
| import org.apache.guacamole.environment.Environment; | ||||
| import org.apache.guacamole.auth.cas.group.GroupParser; | ||||
| import org.apache.guacamole.auth.cas.group.LDAPGroupParser; | ||||
| import org.apache.guacamole.auth.cas.group.PlainGroupParser; | ||||
|  | ||||
| /** | ||||
|  * Service for retrieving configuration information regarding the CAS service. | ||||
| @@ -85,4 +91,102 @@ public class ConfigurationService { | ||||
|         return environment.getProperty(CASGuacamoleProperties.CAS_CLEARPASS_KEY); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the CAS attribute that should be used to determine group | ||||
|      * memberships in CAS, such as "memberOf". If no attribute has been | ||||
|      * specified, null is returned. | ||||
|      * | ||||
|      * @return | ||||
|      *     The attribute name used to determine group memberships in CAS, | ||||
|      *     null if not defined. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If guacamole.properties cannot be parsed. | ||||
|      */ | ||||
|     public String getGroupAttribute() throws GuacamoleException { | ||||
|         return environment.getProperty(CASGuacamoleProperties.CAS_GROUP_ATTRIBUTE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the format that CAS is expected to use for its group names, such | ||||
|      * as {@link GroupFormat#PLAIN} (simple plain-text names) or | ||||
|      * {@link GroupFormat#LDAP} (fully-qualified LDAP DNs). If not specified, | ||||
|      * PLAIN is used by default. | ||||
|      * | ||||
|      * @return | ||||
|      *     The format that CAS is expected to use for its group names. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the format specified within guacamole.properties is not valid. | ||||
|      */ | ||||
|     public GroupFormat getGroupFormat() throws GuacamoleException { | ||||
|         return environment.getProperty(CASGuacamoleProperties.CAS_GROUP_FORMAT, GroupFormat.PLAIN); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the base DN that all LDAP-formatted CAS groups must reside | ||||
|      * beneath. Any groups that are not beneath this base DN should be ignored. | ||||
|      * If no such base DN is provided, the tree structure of the ancestors of | ||||
|      * LDAP-formatted CAS groups should not be considered. | ||||
|      * | ||||
|      * @return | ||||
|      *     The base DN that all LDAP-formatted CAS groups must reside beneath, | ||||
|      *     or null if the tree structure of the ancestors of LDAP-formatted | ||||
|      *     CAS groups should not be considered. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the provided base DN is not a valid LDAP DN. | ||||
|      */ | ||||
|     public LdapName getGroupLDAPBaseDN() throws GuacamoleException { | ||||
|         return environment.getProperty(CASGuacamoleProperties.CAS_GROUP_LDAP_BASE_DN); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns the LDAP attribute that should be required for all LDAP-formatted | ||||
|      * CAS groups. Any groups that do not use this attribute as the last | ||||
|      * (leftmost) attribute of their DN should be ignored. If no such LDAP | ||||
|      * attribute is provided, the last (leftmost) attribute should still be | ||||
|      * used to determine the group name, but the specific attribute involved | ||||
|      * should not be considered. | ||||
|      * | ||||
|      * @return | ||||
|      *     The LDAP attribute that should be required for all LDAP-formatted | ||||
|      *     CAS groups, or null if any attribute should be allowed. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If guacamole.properties cannot be parsed. | ||||
|      */ | ||||
|     public String getGroupLDAPAttribute() throws GuacamoleException { | ||||
|         return environment.getProperty(CASGuacamoleProperties.CAS_GROUP_LDAP_ATTRIBUTE); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Returns a GroupParser instance that can be used to parse CAS group | ||||
|      * names. The parser returned will take into account the configured CAS | ||||
|      * group format, as well as any configured LDAP-specific restrictions. | ||||
|      * | ||||
|      * @return | ||||
|      *     A GroupParser instance that can be used to parse CAS group names as | ||||
|      *     configured in guacamole.properties. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If guacamole.properties cannot be parsed. | ||||
|      */ | ||||
|     public GroupParser getGroupParser() throws GuacamoleException { | ||||
|         switch (getGroupFormat()) { | ||||
|  | ||||
|             // Simple, plain-text groups | ||||
|             case PLAIN: | ||||
|                 return new PlainGroupParser(); | ||||
|  | ||||
|             // LDAP DNs | ||||
|             case LDAP: | ||||
|                 return new LDAPGroupParser(getGroupLDAPAttribute(), getGroupLDAPBaseDN()); | ||||
|  | ||||
|             default: | ||||
|                 throw new GuacamoleServerException("Unsupported CAS group format: " + getGroupFormat()); | ||||
|  | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,49 @@ | ||||
| /* | ||||
|  * 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.auth.cas.conf; | ||||
|  | ||||
| import javax.naming.InvalidNameException; | ||||
| import javax.naming.ldap.LdapName; | ||||
| import org.apache.guacamole.properties.GuacamoleProperty; | ||||
| import org.apache.guacamole.GuacamoleServerException; | ||||
|  | ||||
| /** | ||||
|  * A GuacamoleProperty whose value is an LDAP DN. | ||||
|  */ | ||||
| public abstract class LdapNameGuacamoleProperty implements GuacamoleProperty<LdapName>  { | ||||
|  | ||||
|     @Override | ||||
|     public LdapName parseValue(String value) throws GuacamoleServerException { | ||||
|  | ||||
|         // Consider null/empty values to be empty | ||||
|         if (value == null || value.isEmpty()) | ||||
|             return null; | ||||
|  | ||||
|         // Parse provided value as an LDAP DN | ||||
|         try { | ||||
|             return new LdapName(value); | ||||
|         } | ||||
|         catch (InvalidNameException e) { | ||||
|             throw new GuacamoleServerException("Invalid LDAP distinguished name.", e); | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,41 @@ | ||||
| /* | ||||
|  * 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.auth.cas.group; | ||||
|  | ||||
| import org.apache.guacamole.properties.EnumGuacamoleProperty.PropertyValue; | ||||
|  | ||||
| /** | ||||
|  * Possible formats of group names received from CAS. | ||||
|  */ | ||||
| public enum GroupFormat { | ||||
|  | ||||
|     /** | ||||
|      * Simple, plain-text group names. | ||||
|      */ | ||||
|     @PropertyValue("plain") | ||||
|     PLAIN, | ||||
|  | ||||
|     /** | ||||
|      * Group names formatted as LDAP DNs. | ||||
|      */ | ||||
|     @PropertyValue("ldap") | ||||
|     LDAP | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,44 @@ | ||||
| /* | ||||
|  * 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.auth.cas.group; | ||||
|  | ||||
| /** | ||||
|  * Parser which converts the group names returned by CAS into names usable by | ||||
|  * Guacamole. The format of a CAS group name may vary by the underlying | ||||
|  * authentication backend. For example, a CAS deployment backed by LDAP may | ||||
|  * provide group names as LDAP DNs, which must be transformed into normal group | ||||
|  * names to be usable within Guacamole. | ||||
|  * | ||||
|  * @see LDAPGroupParser | ||||
|  */ | ||||
| public interface GroupParser { | ||||
|  | ||||
|     /** | ||||
|      * Parses the given CAS group name into a group name usable by Guacamole. | ||||
|      * | ||||
|      * @param casGroup | ||||
|      *     The group name retrieved from CAS. | ||||
|      * | ||||
|      * @return | ||||
|      *     A group name usable by Guacamole, or null if the group is not valid. | ||||
|      */ | ||||
|     String parse(String casGroup); | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,106 @@ | ||||
| /* | ||||
|  * 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.auth.cas.group; | ||||
|  | ||||
| import javax.naming.InvalidNameException; | ||||
| import javax.naming.ldap.LdapName; | ||||
| import javax.naming.ldap.Rdn; | ||||
| import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | ||||
|  | ||||
| /** | ||||
|  * GroupParser that converts group names from LDAP DNs into normal group names, | ||||
|  * using the last (leftmost) attribute of the DN as the name. Groups may | ||||
|  * optionally be restricted to only those beneath a specific base DN, or only | ||||
|  * those using a specific attribute as their last (leftmost) attribute. | ||||
|  */ | ||||
| public class LDAPGroupParser implements GroupParser { | ||||
|  | ||||
|     /** | ||||
|      * Logger for this class. | ||||
|      */ | ||||
|     private static final Logger logger = LoggerFactory.getLogger(LDAPGroupParser.class); | ||||
|  | ||||
|     /** | ||||
|      * The LDAP attribute to require for all accepted group names. If null, any | ||||
|      * LDAP attribute will be allowed. | ||||
|      */ | ||||
|     private final String nameAttribute; | ||||
|  | ||||
|     /** | ||||
|      * The base DN to require for all accepted group names. If null, ancestor | ||||
|      * tree structure will not be considered in accepting/rejecting a group. | ||||
|      */ | ||||
|     private final LdapName baseDn; | ||||
|  | ||||
|     /** | ||||
|      * Creates a new LDAPGroupParser which applies the given restrictions on | ||||
|      * any provided group names. | ||||
|      * | ||||
|      * @param nameAttribute | ||||
|      *     The LDAP attribute to require for all accepted group names. This | ||||
|      *     restriction applies to the last (leftmost) attribute only, which is | ||||
|      *     always used to determine the name of the group. If null, any LDAP | ||||
|      *     attribute will be allowed in the last (leftmost) position. | ||||
|      * | ||||
|      * @param baseDn | ||||
|      *     The base DN to require for all accepted group names. If null, | ||||
|      *     ancestor tree structure will not be considered in | ||||
|      *     accepting/rejecting a group. | ||||
|      */ | ||||
|     public LDAPGroupParser(String nameAttribute, LdapName baseDn) { | ||||
|         this.nameAttribute = nameAttribute; | ||||
|         this.baseDn = baseDn; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String parse(String casGroup) { | ||||
|  | ||||
|         // Reject null/empty group names | ||||
|         if (casGroup == null || casGroup.isEmpty()) | ||||
|             return null; | ||||
|  | ||||
|         // Parse group as an LDAP DN | ||||
|         LdapName group; | ||||
|         try { | ||||
|             group = new LdapName(casGroup); | ||||
|         } | ||||
|         catch (InvalidNameException e) { | ||||
|             logger.debug("CAS group \"{}\" has been rejected as it is not a " | ||||
|                     + "valid LDAP DN.", casGroup, e); | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         // Reject any group that is not beneath the base DN | ||||
|         if (baseDn != null && !group.startsWith(baseDn)) | ||||
|             return null; | ||||
|  | ||||
|         // If a specific name attribute is defined, restrict to groups that | ||||
|         // use that attribute to distinguish themselves | ||||
|         Rdn last = group.getRdn(group.size() - 1); | ||||
|         if (nameAttribute != null && !nameAttribute.equalsIgnoreCase(last.getType())) | ||||
|             return null; | ||||
|  | ||||
|         // The group name is the string value of the final attribute in the DN | ||||
|         return last.getValue().toString(); | ||||
|  | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,32 @@ | ||||
| /* | ||||
|  * 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.auth.cas.group; | ||||
|  | ||||
| /** | ||||
|  * GroupParser which simply passes through all CAS group names untouched. | ||||
|  */ | ||||
| public class PlainGroupParser implements GroupParser { | ||||
|  | ||||
|     @Override | ||||
|     public String parse(String casGroup) { | ||||
|         return casGroup; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -21,6 +21,7 @@ package org.apache.guacamole.auth.cas.ticket; | ||||
|  | ||||
| import com.google.common.io.BaseEncoding; | ||||
| import com.google.inject.Inject; | ||||
| import com.google.inject.Provider; | ||||
| import java.net.URI; | ||||
| import java.security.InvalidKeyException; | ||||
| import java.security.NoSuchAlgorithmException; | ||||
| @@ -30,13 +31,17 @@ import javax.crypto.Cipher; | ||||
| import javax.crypto.IllegalBlockSizeException; | ||||
| import javax.crypto.NoSuchPaddingException; | ||||
| import java.nio.charset.Charset; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.Map.Entry; | ||||
| import java.util.Set; | ||||
| import java.util.stream.Collectors; | ||||
| import org.apache.guacamole.GuacamoleException; | ||||
| import org.apache.guacamole.GuacamoleSecurityException; | ||||
| import org.apache.guacamole.GuacamoleServerException; | ||||
| import org.apache.guacamole.auth.cas.conf.ConfigurationService; | ||||
| import org.apache.guacamole.auth.cas.user.CASAuthenticatedUser; | ||||
| import org.apache.guacamole.net.auth.Credentials; | ||||
| import org.apache.guacamole.token.TokenName; | ||||
| import org.jasig.cas.client.authentication.AttributePrincipal; | ||||
| @@ -68,6 +73,46 @@ public class TicketValidationService { | ||||
|     @Inject | ||||
|     private ConfigurationService confService; | ||||
|  | ||||
|     /** | ||||
|      * Provider for AuthenticatedUser objects. | ||||
|      */ | ||||
|     @Inject | ||||
|     private Provider<CASAuthenticatedUser> authenticatedUserProvider; | ||||
|  | ||||
|     /** | ||||
|      * Converts the given CAS attribute value object (whose type is variable) | ||||
|      * to a Set of String values. If the value is already a Collection of some | ||||
|      * kind, its values are converted to Strings and returned as the members of | ||||
|      * the Set. If the value is not already a Collection, it is assumed to be a | ||||
|      * single value, converted to a String, and used as the sole member of the | ||||
|      * set. | ||||
|      * | ||||
|      * @param obj | ||||
|      *     The CAS attribute value to convert to a Set of Strings. | ||||
|      * | ||||
|      * @return | ||||
|      *     A Set of all String values contained within the given CAS attribute | ||||
|      *     value. | ||||
|      */ | ||||
|     private Set<String> toStringSet(Object obj) { | ||||
|  | ||||
|         // Consider null to represent no provided values | ||||
|         if (obj == null) | ||||
|             return Collections.emptySet(); | ||||
|  | ||||
|         // If the provided object is already a Collection, produce a Collection | ||||
|         // where we know for certain that all values are Strings | ||||
|         if (obj instanceof Collection) { | ||||
|             return ((Collection<?>) obj).stream() | ||||
|                     .map(Object::toString) | ||||
|                     .collect(Collectors.toSet()); | ||||
|         } | ||||
|  | ||||
|         // Otherwise, assume we have only a single value | ||||
|         return Collections.singleton(obj.toString()); | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Validates and parses the given ID ticket, returning a map of all | ||||
|      * available tokens for the given user based on attributes provided by the | ||||
| @@ -81,62 +126,80 @@ public class TicketValidationService { | ||||
|      *     password values in. | ||||
|      * | ||||
|      * @return | ||||
|      *     A Map all of tokens for the user parsed from attributes returned | ||||
|      *     by the CAS server. | ||||
|      *     A CASAuthenticatedUser instance containing the ticket data returned by the CAS server. | ||||
|      * | ||||
|      * @throws GuacamoleException | ||||
|      *     If the ID ticket is not valid or guacamole.properties could | ||||
|      *     not be parsed. | ||||
|      */ | ||||
|     public Map<String, String> validateTicket(String ticket, | ||||
|     public CASAuthenticatedUser validateTicket(String ticket, | ||||
|             Credentials credentials) throws GuacamoleException { | ||||
|  | ||||
|         // Retrieve the configured CAS URL, establish a ticket validator, | ||||
|         // and then attempt to validate the supplied ticket.  If that succeeds, | ||||
|         // grab the principal returned by the validator. | ||||
|         // Create a ticket validator that uses the configured CAS URL | ||||
|         URI casServerUrl = confService.getAuthorizationEndpoint(); | ||||
|         Cas20ProxyTicketValidator validator = new Cas20ProxyTicketValidator(casServerUrl.toString()); | ||||
|         validator.setAcceptAnyProxy(true); | ||||
|         validator.setEncoding("UTF-8"); | ||||
|  | ||||
|         // Attempt to validate the supplied ticket | ||||
|         Assertion assertion; | ||||
|         try { | ||||
|             Map<String, String> tokens = new HashMap<>(); | ||||
|             URI confRedirectURI = confService.getRedirectURI(); | ||||
|             Assertion a = validator.validate(ticket, confRedirectURI.toString()); | ||||
|             AttributePrincipal principal =  a.getPrincipal(); | ||||
|             Map<String, Object> ticketAttrs = | ||||
|                     new HashMap<>(principal.getAttributes()); | ||||
|  | ||||
|             // Retrieve username and set the credentials. | ||||
|             String username = principal.getName(); | ||||
|             if (username == null) | ||||
|                 throw new GuacamoleSecurityException("No username provided by CAS."); | ||||
|              | ||||
|             credentials.setUsername(username); | ||||
|  | ||||
|             // Retrieve password, attempt decryption, and set credentials. | ||||
|             Object credObj = ticketAttrs.remove("credential"); | ||||
|             if (credObj != null) { | ||||
|                 String clearPass = decryptPassword(credObj.toString()); | ||||
|                 if (clearPass != null && !clearPass.isEmpty()) | ||||
|                     credentials.setPassword(clearPass); | ||||
|             } | ||||
|              | ||||
|             // Convert remaining attributes that have values to Strings | ||||
|             for (Entry <String, Object> attr : ticketAttrs.entrySet()) { | ||||
|                 String tokenName = TokenName.canonicalize(attr.getKey(), | ||||
|                         CAS_ATTRIBUTE_TOKEN_PREFIX); | ||||
|                 Object value = attr.getValue(); | ||||
|                 if (value != null) | ||||
|                     tokens.put(tokenName, value.toString()); | ||||
|             } | ||||
|  | ||||
|             return tokens; | ||||
|  | ||||
|         }  | ||||
|             assertion = validator.validate(ticket, confRedirectURI.toString()); | ||||
|         } | ||||
|         catch (TicketValidationException e) { | ||||
|             throw new GuacamoleException("Ticket validation failed.", e); | ||||
|         } | ||||
|  | ||||
|         // Pull user principal and associated attributes | ||||
|         AttributePrincipal principal =  assertion.getPrincipal(); | ||||
|         Map<String, Object> ticketAttrs = new HashMap<>(principal.getAttributes()); | ||||
|  | ||||
|         // Retrieve user identity from principal | ||||
|         String username = principal.getName(); | ||||
|         if (username == null) | ||||
|             throw new GuacamoleSecurityException("No username provided by CAS."); | ||||
|  | ||||
|         // Update credentials with username provided by CAS for sake of | ||||
|         // ${GUAC_USERNAME} token | ||||
|         credentials.setUsername(username); | ||||
|  | ||||
|         // Retrieve password, attempt decryption, and set credentials. | ||||
|         Object credObj = ticketAttrs.remove("credential"); | ||||
|         if (credObj != null) { | ||||
|             String clearPass = decryptPassword(credObj.toString()); | ||||
|             if (clearPass != null && !clearPass.isEmpty()) | ||||
|                 credentials.setPassword(clearPass); | ||||
|         } | ||||
|  | ||||
|         Set<String> effectiveGroups; | ||||
|  | ||||
|         // Parse effective groups from principal attributes if a specific | ||||
|         // group attribute has been configured | ||||
|         String groupAttribute = confService.getGroupAttribute(); | ||||
|         if (groupAttribute != null) { | ||||
|             effectiveGroups = toStringSet(ticketAttrs.get(groupAttribute)).stream() | ||||
|                     .map(confService.getGroupParser()::parse) | ||||
|                     .collect(Collectors.toSet()); | ||||
|         } | ||||
|  | ||||
|         // Otherwise, assume no effective groups | ||||
|         else | ||||
|             effectiveGroups = Collections.emptySet(); | ||||
|  | ||||
|         // Convert remaining attributes that have values to Strings | ||||
|         Map<String, String> tokens = new HashMap<>(ticketAttrs.size()); | ||||
|         ticketAttrs.forEach((key, value) -> { | ||||
|             if (value != null) { | ||||
|                 String tokenName = TokenName.canonicalize(key, CAS_ATTRIBUTE_TOKEN_PREFIX); | ||||
|                 tokens.put(tokenName, value.toString()); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         CASAuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); | ||||
|         authenticatedUser.init(username, credentials, tokens, effectiveGroups); | ||||
|         return authenticatedUser; | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
| @@ -22,6 +22,7 @@ package org.apache.guacamole.auth.cas.user; | ||||
| import com.google.inject.Inject; | ||||
| import java.util.Collections; | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
| import org.apache.guacamole.net.auth.AbstractAuthenticatedUser; | ||||
| import org.apache.guacamole.net.auth.AuthenticationProvider; | ||||
| import org.apache.guacamole.net.auth.Credentials; | ||||
| @@ -50,6 +51,11 @@ public class CASAuthenticatedUser extends AbstractAuthenticatedUser { | ||||
|      */ | ||||
|     private Map<String, String> tokens; | ||||
|  | ||||
|     /** | ||||
|      * The unique identifiers of all user groups which this user is a member of. | ||||
|      */ | ||||
|     private Set<String> effectiveGroups; | ||||
|  | ||||
|     /** | ||||
|      * Initializes this AuthenticatedUser using the given username and | ||||
|      * credentials, and an empty map of parameter tokens. | ||||
| @@ -61,7 +67,7 @@ public class CASAuthenticatedUser extends AbstractAuthenticatedUser { | ||||
|      *     The credentials provided when this user was authenticated. | ||||
|      */ | ||||
|     public void init(String username, Credentials credentials) { | ||||
|         this.init(username, credentials, Collections.emptyMap()); | ||||
|         this.init(username, credentials, Collections.emptyMap(), Collections.emptySet()); | ||||
|     } | ||||
|      | ||||
|     /** | ||||
| @@ -79,9 +85,10 @@ public class CASAuthenticatedUser extends AbstractAuthenticatedUser { | ||||
|      *     as tokens when connections are established with this user. | ||||
|      */ | ||||
|     public void init(String username, Credentials credentials, | ||||
|             Map<String, String> tokens) { | ||||
|             Map<String, String> tokens, Set<String> effectiveGroups) { | ||||
|         this.credentials = credentials; | ||||
|         this.tokens = Collections.unmodifiableMap(tokens); | ||||
|         this.effectiveGroups = effectiveGroups; | ||||
|         setIdentifier(username.toLowerCase()); | ||||
|     } | ||||
|  | ||||
| @@ -107,4 +114,9 @@ public class CASAuthenticatedUser extends AbstractAuthenticatedUser { | ||||
|         return credentials; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Set<String> getEffectiveUserGroups() { | ||||
|         return effectiveGroups; | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user