mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-07 05:31:22 +00:00
GUACAMOLE-1364: Refactor all SSO extensions beneath common base.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<assembly
|
||||
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
|
||||
|
||||
<id>dist</id>
|
||||
<baseDirectory>${project.artifactId}-${project.version}</baseDirectory>
|
||||
|
||||
<!-- Output tar.gz -->
|
||||
<formats>
|
||||
<format>tar.gz</format>
|
||||
</formats>
|
||||
|
||||
<!-- Include licenses and extension .jar -->
|
||||
<fileSets>
|
||||
|
||||
<!-- Include licenses -->
|
||||
<fileSet>
|
||||
<outputDirectory></outputDirectory>
|
||||
<directory>target/licenses</directory>
|
||||
</fileSet>
|
||||
|
||||
<!-- Include extension .jar -->
|
||||
<fileSet>
|
||||
<directory>target</directory>
|
||||
<outputDirectory></outputDirectory>
|
||||
<includes>
|
||||
<include>*.jar</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
|
||||
</fileSets>
|
||||
|
||||
</assembly>
|
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import java.util.Arrays;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.apache.guacamole.form.Field;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.auth.Credentials;
|
||||
import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
|
||||
import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
|
||||
import org.apache.guacamole.auth.cas.conf.ConfigurationService;
|
||||
import org.apache.guacamole.auth.cas.form.CASTicketField;
|
||||
import org.apache.guacamole.auth.cas.ticket.TicketValidationService;
|
||||
import org.apache.guacamole.auth.cas.user.CASAuthenticatedUser;
|
||||
import org.apache.guacamole.language.TranslatableMessage;
|
||||
|
||||
/**
|
||||
* Service providing convenience functions for the CAS AuthenticationProvider
|
||||
* implementation.
|
||||
*/
|
||||
public class AuthenticationProviderService {
|
||||
|
||||
/**
|
||||
* Service for retrieving CAS configuration information.
|
||||
*/
|
||||
@Inject
|
||||
private ConfigurationService confService;
|
||||
|
||||
/**
|
||||
* Service for validating received ID tickets.
|
||||
*/
|
||||
@Inject
|
||||
private TicketValidationService ticketService;
|
||||
|
||||
/**
|
||||
* Returns an AuthenticatedUser representing the user authenticated by the
|
||||
* given credentials.
|
||||
*
|
||||
* @param credentials
|
||||
* The credentials to use for authentication.
|
||||
*
|
||||
* @return
|
||||
* A CASAuthenticatedUser representing the user authenticated by the
|
||||
* given credentials.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while authenticating the user, or if access is
|
||||
* denied.
|
||||
*/
|
||||
public CASAuthenticatedUser authenticateUser(Credentials credentials)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Pull CAS ticket from request if present
|
||||
HttpServletRequest request = credentials.getRequest();
|
||||
if (request != null) {
|
||||
String ticket = request.getParameter(CASTicketField.PARAMETER_NAME);
|
||||
if (ticket != null) {
|
||||
return ticketService.validateTicket(ticket, credentials);
|
||||
}
|
||||
}
|
||||
|
||||
// Request CAS ticket
|
||||
throw new GuacamoleInvalidCredentialsException("Invalid login.",
|
||||
new CredentialsInfo(Arrays.asList(new Field[] {
|
||||
|
||||
// CAS-specific ticket (will automatically redirect the user
|
||||
// to the authorization page via JavaScript)
|
||||
new CASTicketField(
|
||||
confService.getAuthorizationEndpoint(),
|
||||
confService.getRedirectURI(),
|
||||
new TranslatableMessage("LOGIN.INFO_CAS_REDIRECT_PENDING")
|
||||
)
|
||||
|
||||
}))
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.auth.cas.user.CASAuthenticatedUser;
|
||||
import org.apache.guacamole.net.auth.AbstractAuthenticationProvider;
|
||||
import org.apache.guacamole.net.auth.AuthenticatedUser;
|
||||
import org.apache.guacamole.net.auth.Credentials;
|
||||
import org.apache.guacamole.net.auth.TokenInjectingUserContext;
|
||||
import org.apache.guacamole.net.auth.UserContext;
|
||||
|
||||
/**
|
||||
* Guacamole authentication backend which authenticates users using an
|
||||
* arbitrary external system implementing CAS. No storage for connections is
|
||||
* provided - only authentication. Storage must be provided by some other
|
||||
* extension.
|
||||
*/
|
||||
public class CASAuthenticationProvider extends AbstractAuthenticationProvider {
|
||||
|
||||
/**
|
||||
* Injector which will manage the object graph of this authentication
|
||||
* provider.
|
||||
*/
|
||||
private final Injector injector;
|
||||
|
||||
/**
|
||||
* Creates a new CASAuthenticationProvider that authenticates users
|
||||
* against an CAS service
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If a required property is missing, or an error occurs while parsing
|
||||
* a property.
|
||||
*/
|
||||
public CASAuthenticationProvider() throws GuacamoleException {
|
||||
|
||||
// Set up Guice injector.
|
||||
injector = Guice.createInjector(
|
||||
new CASAuthenticationProviderModule(this)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIdentifier() {
|
||||
return "cas";
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticatedUser authenticateUser(Credentials credentials)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Attempt to authenticate user with given credentials
|
||||
AuthenticationProviderService authProviderService = injector.getInstance(AuthenticationProviderService.class);
|
||||
return authProviderService.authenticateUser(credentials);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserContext decorate(UserContext context,
|
||||
AuthenticatedUser authenticatedUser, Credentials credentials)
|
||||
throws GuacamoleException {
|
||||
|
||||
if (!(authenticatedUser instanceof CASAuthenticatedUser))
|
||||
return context;
|
||||
|
||||
return new TokenInjectingUserContext(context,
|
||||
((CASAuthenticatedUser) authenticatedUser).getTokens());
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.apache.guacamole.auth.cas.conf.ConfigurationService;
|
||||
import com.google.inject.AbstractModule;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.environment.Environment;
|
||||
import org.apache.guacamole.environment.LocalEnvironment;
|
||||
import org.apache.guacamole.net.auth.AuthenticationProvider;
|
||||
import org.apache.guacamole.auth.cas.ticket.TicketValidationService;
|
||||
|
||||
/**
|
||||
* Guice module which configures CAS-specific injections.
|
||||
*/
|
||||
public class CASAuthenticationProviderModule extends AbstractModule {
|
||||
|
||||
/**
|
||||
* Guacamole server environment.
|
||||
*/
|
||||
private final Environment environment;
|
||||
|
||||
/**
|
||||
* A reference to the CASAuthenticationProvider on behalf of which this
|
||||
* module has configured injection.
|
||||
*/
|
||||
private final AuthenticationProvider authProvider;
|
||||
|
||||
/**
|
||||
* Creates a new CAS authentication provider module which configures
|
||||
* injection for the CASAuthenticationProvider.
|
||||
*
|
||||
* @param authProvider
|
||||
* The AuthenticationProvider for which injection is being configured.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while retrieving the Guacamole server
|
||||
* environment.
|
||||
*/
|
||||
public CASAuthenticationProviderModule(AuthenticationProvider authProvider)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Get local environment
|
||||
this.environment = LocalEnvironment.getInstance();
|
||||
|
||||
// Store associated auth provider
|
||||
this.authProvider = authProvider;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
|
||||
// Bind core implementations of guacamole-ext classes
|
||||
bind(AuthenticationProvider.class).toInstance(authProvider);
|
||||
bind(Environment.class).toInstance(environment);
|
||||
|
||||
// Bind CAS-specific services
|
||||
bind(ConfigurationService.class);
|
||||
bind(TicketValidationService.class);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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 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.
|
||||
* These properties will be read from guacamole.properties when the CAS
|
||||
* authentication provider is used.
|
||||
*/
|
||||
public class CASGuacamoleProperties {
|
||||
|
||||
/**
|
||||
* This class should not be instantiated.
|
||||
*/
|
||||
private CASGuacamoleProperties() {}
|
||||
|
||||
/**
|
||||
* The authorization endpoint (URI) of the CAS service.
|
||||
*/
|
||||
public static final URIGuacamoleProperty CAS_AUTHORIZATION_ENDPOINT =
|
||||
new URIGuacamoleProperty() {
|
||||
|
||||
@Override
|
||||
public String getName() { return "cas-authorization-endpoint"; }
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The URI that the CAS service should redirect to after the
|
||||
* authentication process is complete. This must be the full URL that a
|
||||
* user would enter into their browser to access Guacamole.
|
||||
*/
|
||||
public static final URIGuacamoleProperty CAS_REDIRECT_URI =
|
||||
new URIGuacamoleProperty() {
|
||||
|
||||
@Override
|
||||
public String getName() { return "cas-redirect-uri"; }
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The location of the private key file used to retrieve the
|
||||
* password if CAS is configured to support ClearPass.
|
||||
*/
|
||||
public static final PrivateKeyGuacamoleProperty CAS_CLEARPASS_KEY =
|
||||
new PrivateKeyGuacamoleProperty() {
|
||||
|
||||
@Override
|
||||
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"; }
|
||||
|
||||
};
|
||||
|
||||
}
|
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* 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 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.
|
||||
*/
|
||||
public class ConfigurationService {
|
||||
|
||||
/**
|
||||
* The Guacamole server environment.
|
||||
*/
|
||||
@Inject
|
||||
private Environment environment;
|
||||
|
||||
/**
|
||||
* Returns the authorization endpoint (URI) of the CAS service as
|
||||
* configured with guacamole.properties.
|
||||
*
|
||||
* @return
|
||||
* The authorization endpoint of the CAS service, as configured with
|
||||
* guacamole.properties.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If guacamole.properties cannot be parsed, or if the authorization
|
||||
* endpoint property is missing.
|
||||
*/
|
||||
public URI getAuthorizationEndpoint() throws GuacamoleException {
|
||||
return environment.getRequiredProperty(CASGuacamoleProperties.CAS_AUTHORIZATION_ENDPOINT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URI that the CAS service should redirect to after
|
||||
* the authentication process is complete, as configured with
|
||||
* guacamole.properties. This must be the full URL that a user would enter
|
||||
* into their browser to access Guacamole.
|
||||
*
|
||||
* @return
|
||||
* The URI to redirect the client back to after authentication
|
||||
* is completed, as configured in guacamole.properties.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If guacamole.properties cannot be parsed, or if the redirect URI
|
||||
* property is missing.
|
||||
*/
|
||||
public URI getRedirectURI() throws GuacamoleException {
|
||||
return environment.getRequiredProperty(CASGuacamoleProperties.CAS_REDIRECT_URI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the PrivateKey used to decrypt the credential object
|
||||
* sent encrypted by CAS, or null if no key is defined.
|
||||
*
|
||||
* @return
|
||||
* The PrivateKey used to decrypt the ClearPass
|
||||
* credential returned by CAS.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If guacamole.properties cannot be parsed.
|
||||
*/
|
||||
public PrivateKey getClearpassKey() throws GuacamoleException {
|
||||
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,88 @@
|
||||
/*
|
||||
* 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 java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import org.apache.guacamole.properties.GuacamoleProperty;
|
||||
import org.apache.guacamole.GuacamoleServerException;
|
||||
|
||||
/**
|
||||
* A GuacamoleProperty whose value is derived from a private key file.
|
||||
*/
|
||||
public abstract class PrivateKeyGuacamoleProperty implements GuacamoleProperty<PrivateKey> {
|
||||
|
||||
@Override
|
||||
public PrivateKey parseValue(String value) throws GuacamoleServerException {
|
||||
|
||||
if (value == null || value.isEmpty())
|
||||
return null;
|
||||
|
||||
FileInputStream keyStreamIn = null;
|
||||
|
||||
try {
|
||||
try {
|
||||
|
||||
// Open and read the file specified in the configuration.
|
||||
File keyFile = new File(value);
|
||||
keyStreamIn = new FileInputStream(keyFile);
|
||||
ByteArrayOutputStream keyStreamOut = new ByteArrayOutputStream();
|
||||
byte[] keyBuffer = new byte[1024];
|
||||
|
||||
for (int readBytes; (readBytes = keyStreamIn.read(keyBuffer)) != -1;)
|
||||
keyStreamOut.write(keyBuffer, 0, readBytes);
|
||||
|
||||
final byte[] keyBytes = keyStreamOut.toByteArray();
|
||||
|
||||
// Set up decryption infrastructure
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
KeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
|
||||
return keyFactory.generatePrivate(keySpec);
|
||||
|
||||
}
|
||||
catch (FileNotFoundException e) {
|
||||
throw new GuacamoleServerException("Could not find the specified key file.", e);
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
throw new GuacamoleServerException("RSA algorithm is not available.", e);
|
||||
}
|
||||
catch (InvalidKeySpecException e) {
|
||||
throw new GuacamoleServerException("Key is not in expected PKCS8 encoding.", e);
|
||||
}
|
||||
finally {
|
||||
if (keyStreamIn != null)
|
||||
keyStreamIn.close();
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new GuacamoleServerException("Could not read in the specified key file.", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.form;
|
||||
|
||||
import java.net.URI;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import org.apache.guacamole.form.RedirectField;
|
||||
import org.apache.guacamole.language.TranslatableMessage;
|
||||
|
||||
|
||||
/**
|
||||
* Field definition which represents the ticket returned by an CAS service.
|
||||
* This is processed transparently - the user is redirected to CAS, authenticates
|
||||
* and then is returned to Guacamole where the ticket field is
|
||||
* processed.
|
||||
*/
|
||||
public class CASTicketField extends RedirectField {
|
||||
|
||||
/**
|
||||
* The parameter that will be present upon successful CAS authentication.
|
||||
*/
|
||||
public static final String PARAMETER_NAME = "ticket";
|
||||
|
||||
/**
|
||||
* The standard URI name for the CAS login resource.
|
||||
*/
|
||||
private static final String CAS_LOGIN_URI = "login";
|
||||
|
||||
/**
|
||||
* Creates a new CAS "ticket" field which links to the given CAS
|
||||
* service using the provided client ID. Successful authentication at the
|
||||
* CAS service will result in the client being redirected to the specified
|
||||
* redirect URI. The CAS ticket will be embedded in the fragment (the part
|
||||
* following the hash symbol) of that URI, which the JavaScript side of
|
||||
* this extension will move to the query parameters.
|
||||
*
|
||||
* @param authorizationEndpoint
|
||||
* The full URL of the endpoint accepting CAS authentication
|
||||
* requests.
|
||||
*
|
||||
* @param redirectURI
|
||||
* The URI that the CAS service should redirect to upon successful
|
||||
* authentication.
|
||||
*
|
||||
* @param redirectMessage
|
||||
* The message that will be displayed for the user while the redirect
|
||||
* is processed. This will be processed through Guacamole's translation
|
||||
* system.
|
||||
*/
|
||||
public CASTicketField(URI authorizationEndpoint, URI redirectURI,
|
||||
TranslatableMessage redirectMessage) {
|
||||
|
||||
super(PARAMETER_NAME, UriBuilder.fromUri(authorizationEndpoint)
|
||||
.path(CAS_LOGIN_URI)
|
||||
.queryParam("service", redirectURI)
|
||||
.build(),
|
||||
redirectMessage);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,271 @@
|
||||
/*
|
||||
* 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.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;
|
||||
import java.security.PrivateKey;
|
||||
import javax.crypto.BadPaddingException;
|
||||
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.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;
|
||||
import org.jasig.cas.client.validation.Assertion;
|
||||
import org.jasig.cas.client.validation.Cas20ProxyTicketValidator;
|
||||
import org.jasig.cas.client.validation.TicketValidationException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Service for validating ID tickets forwarded to us by the client, verifying
|
||||
* that they did indeed come from the CAS service.
|
||||
*/
|
||||
public class TicketValidationService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(TicketValidationService.class);
|
||||
|
||||
/**
|
||||
* The prefix to use when generating token names.
|
||||
*/
|
||||
public static final String CAS_ATTRIBUTE_TOKEN_PREFIX = "CAS_";
|
||||
|
||||
/**
|
||||
* Service for retrieving CAS configuration information.
|
||||
*/
|
||||
@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
|
||||
* CAS server. If the ticket is invalid an exception is thrown.
|
||||
*
|
||||
* @param ticket
|
||||
* The ID ticket to validate and parse.
|
||||
*
|
||||
* @param credentials
|
||||
* The Credentials object to store retrieved username and
|
||||
* password values in.
|
||||
*
|
||||
* @return
|
||||
* 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 CASAuthenticatedUser validateTicket(String ticket,
|
||||
Credentials credentials) throws GuacamoleException {
|
||||
|
||||
// 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 {
|
||||
URI confRedirectURI = confService.getRedirectURI();
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes an encrypted string representing a password provided by
|
||||
* the CAS ClearPass service and decrypts it using the private
|
||||
* key configured for this extension. Returns null if it is
|
||||
* unable to decrypt the password.
|
||||
*
|
||||
* @param encryptedPassword
|
||||
* A string with the encrypted password provided by the
|
||||
* CAS service.
|
||||
*
|
||||
* @return
|
||||
* The decrypted password, or null if it is unable to
|
||||
* decrypt the password.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If unable to get Guacamole configuration data
|
||||
*/
|
||||
private final String decryptPassword(String encryptedPassword)
|
||||
throws GuacamoleException {
|
||||
|
||||
// If we get nothing, we return nothing.
|
||||
if (encryptedPassword == null || encryptedPassword.isEmpty()) {
|
||||
logger.warn("No or empty encrypted password, no password will be available.");
|
||||
return null;
|
||||
}
|
||||
|
||||
final PrivateKey clearpassKey = confService.getClearpassKey();
|
||||
if (clearpassKey == null) {
|
||||
logger.debug("No private key available to decrypt password.");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
final Cipher cipher = Cipher.getInstance(clearpassKey.getAlgorithm());
|
||||
|
||||
if (cipher == null)
|
||||
throw new GuacamoleServerException("Failed to initialize cipher object with private key.");
|
||||
|
||||
// Initialize the Cipher in decrypt mode.
|
||||
cipher.init(Cipher.DECRYPT_MODE, clearpassKey);
|
||||
|
||||
// Decode and decrypt, and return a new string.
|
||||
final byte[] pass64 = BaseEncoding.base64().decode(encryptedPassword);
|
||||
final byte[] cipherData = cipher.doFinal(pass64);
|
||||
return new String(cipherData, Charset.forName("UTF-8"));
|
||||
|
||||
}
|
||||
catch (BadPaddingException e) {
|
||||
throw new GuacamoleServerException("Bad padding when decrypting cipher data.", e);
|
||||
}
|
||||
catch (IllegalBlockSizeException e) {
|
||||
throw new GuacamoleServerException("Illegal block size while opening private key.", e);
|
||||
}
|
||||
catch (InvalidKeyException e) {
|
||||
throw new GuacamoleServerException("Specified private key for ClearPass decryption is invalid.", e);
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
throw new GuacamoleServerException("Unexpected algorithm for the private key.", e);
|
||||
}
|
||||
catch (NoSuchPaddingException e) {
|
||||
throw new GuacamoleServerException("No such padding trying to initialize cipher with private key.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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.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;
|
||||
|
||||
/**
|
||||
* An CAS-specific implementation of AuthenticatedUser, associating a
|
||||
* username and particular set of credentials with the CAS authentication
|
||||
* provider.
|
||||
*/
|
||||
public class CASAuthenticatedUser extends AbstractAuthenticatedUser {
|
||||
|
||||
/**
|
||||
* Reference to the authentication provider associated with this
|
||||
* authenticated user.
|
||||
*/
|
||||
@Inject
|
||||
private AuthenticationProvider authProvider;
|
||||
|
||||
/**
|
||||
* The credentials provided when this user was authenticated.
|
||||
*/
|
||||
private Credentials credentials;
|
||||
|
||||
/**
|
||||
* Tokens associated with this authenticated user.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user that was authenticated.
|
||||
*
|
||||
* @param credentials
|
||||
* The credentials provided when this user was authenticated.
|
||||
*/
|
||||
public void init(String username, Credentials credentials) {
|
||||
this.init(username, credentials, Collections.emptyMap(), Collections.emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes this AuthenticatedUser using the given username,
|
||||
* credentials, and parameter tokens.
|
||||
*
|
||||
* @param username
|
||||
* The username of the user that was authenticated.
|
||||
*
|
||||
* @param credentials
|
||||
* The credentials provided when this user was authenticated.
|
||||
*
|
||||
* @param tokens
|
||||
* A map of all the name/value pairs that should be available
|
||||
* as tokens when connections are established with this user.
|
||||
*/
|
||||
public void init(String username, Credentials credentials,
|
||||
Map<String, String> tokens, Set<String> effectiveGroups) {
|
||||
this.credentials = credentials;
|
||||
this.tokens = Collections.unmodifiableMap(tokens);
|
||||
this.effectiveGroups = effectiveGroups;
|
||||
setIdentifier(username.toLowerCase());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Map containing the name/value pairs that can be applied
|
||||
* as parameter tokens when connections are established by the user.
|
||||
*
|
||||
* @return
|
||||
* A Map containing all of the name/value pairs that can be
|
||||
* used as parameter tokens by this user.
|
||||
*/
|
||||
public Map<String, String> getTokens() {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AuthenticationProvider getAuthenticationProvider() {
|
||||
return authProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Credentials getCredentials() {
|
||||
return credentials;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getEffectiveUserGroups() {
|
||||
return effectiveGroups;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
{
|
||||
|
||||
"guacamoleVersion" : "1.3.0",
|
||||
|
||||
"name" : "CAS Authentication Extension",
|
||||
"namespace" : "cas",
|
||||
|
||||
"authProviders" : [
|
||||
"org.apache.guacamole.auth.cas.CASAuthenticationProvider"
|
||||
],
|
||||
|
||||
"translations" : [
|
||||
"translations/ca.json",
|
||||
"translations/de.json",
|
||||
"translations/en.json",
|
||||
"translations/fr.json",
|
||||
"translations/ja.json",
|
||||
"translations/ko.json",
|
||||
"translations/pt.json",
|
||||
"translations/ru.json",
|
||||
"translations/zh.json"
|
||||
]
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
@@ -0,0 +1,11 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_CAS" : {
|
||||
"NAME" : "Backend d'inici de sessió unificat (SSO) CAS"
|
||||
},
|
||||
|
||||
"LOGIN" : {
|
||||
"INFO_CAS_REDIRECT_PENDING" : "Espereu, redireccionant a l'autenticació CAS ..."
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_CAS" : {
|
||||
"NAME" : "CAS SSO Backend"
|
||||
},
|
||||
|
||||
"LOGIN" : {
|
||||
"FIELD_HEADER_TICKET" : "",
|
||||
"INFO_CAS_REDIRECT_PENDING" : "Bitte warten, Sie werden zur CAS-Authentifizierung weitergeleitet..."
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_CAS" : {
|
||||
"NAME" : "CAS SSO Backend"
|
||||
},
|
||||
|
||||
"LOGIN" : {
|
||||
"FIELD_HEADER_TICKET" : "",
|
||||
"INFO_CAS_REDIRECT_PENDING" : "Please wait, redirecting to CAS authentication..."
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_CAS" : {
|
||||
"NAME" : "CAS SSO Backend"
|
||||
},
|
||||
|
||||
"LOGIN" : {
|
||||
"FIELD_HEADER_TICKET" : "",
|
||||
"INFO_CAS_REDIRECT_PENDING" : "Veuillez patienter, redirection vers l'authentification CAS..."
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
{
|
||||
|
||||
"LOGIN" : {
|
||||
"INFO_CAS_REDIRECT_PENDING" : "CAS認証にリダイレクトしています。"
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
{
|
||||
|
||||
"LOGIN" : {
|
||||
"INFO_CAS_REDIRECT_PENDING" : "기다려주십시오. CAS 인증으로 리디렉션 중..."
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_CAS" : {
|
||||
"NAME" : "CAS SSO Backend"
|
||||
},
|
||||
|
||||
"LOGIN" : {
|
||||
"FIELD_HEADER_TICKET" : "",
|
||||
"INFO_CAS_REDIRECT_PENDING" : "Por favor aguarde, redirecionando para autenticação CAS..."
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_CAS" : {
|
||||
"NAME" : "Бэкенд CAS SSO"
|
||||
},
|
||||
|
||||
"LOGIN" : {
|
||||
"INFO_CAS_REDIRECT_PENDING" : "Пожалуйста, подождите. Переадресую на страницу аутентификации CAS..."
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
{
|
||||
|
||||
"DATA_SOURCE_CAS" : {
|
||||
"NAME" : "CAS SSO后端"
|
||||
},
|
||||
|
||||
"LOGIN" : {
|
||||
"FIELD_HEADER_TICKET" : "",
|
||||
"INFO_CAS_REDIRECT_PENDING" : "请稍候,正在重定向到CAS验证..."
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user