GUACAMOLE-1364: Allow both traditional username/password and SSO.

This commit is contained in:
Michael Jumper
2021-11-25 19:35:46 -08:00
parent 7dc0b3b509
commit 0e9860ecf7
26 changed files with 360 additions and 208 deletions

View File

@@ -0,0 +1 @@
src/main/resources/html/*.html

View File

@@ -20,25 +20,39 @@
package org.apache.guacamole.auth.cas;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.net.URI;
import java.util.Arrays;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.UriBuilder;
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.sso.SSOAuthenticationProviderService;
import org.apache.guacamole.auth.sso.user.SSOAuthenticatedUser;
import org.apache.guacamole.form.RedirectField;
import org.apache.guacamole.language.TranslatableMessage;
/**
* Service that authenticates Guacamole users by processing CAS tickets.
*/
@Singleton
public class AuthenticationProviderService implements SSOAuthenticationProviderService {
/**
* The parameter that will be present upon successful CAS authentication.
*/
public static final String TICKET_PARAMETER_NAME = "ticket";
/**
* The standard URI name for the CAS login resource.
*/
private static final String CAS_LOGIN_URI = "login";
/**
* Service for retrieving CAS configuration information.
*/
@@ -58,29 +72,42 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS
// Pull CAS ticket from request if present
HttpServletRequest request = credentials.getRequest();
if (request != null) {
String ticket = request.getParameter(CASTicketField.PARAMETER_NAME);
String ticket = request.getParameter(TICKET_PARAMETER_NAME);
if (ticket != null) {
return ticketService.validateTicket(ticket, credentials);
}
}
// Request CAS ticket
// Request CAS ticket (will automatically redirect the user to the
// CAS authorization page via JavaScript)
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_IDP_REDIRECT_PENDING")
)
new RedirectField(TICKET_PARAMETER_NAME, getLoginURI(),
new TranslatableMessage("LOGIN.INFO_IDP_REDIRECT_PENDING"))
}))
);
}
/**
* Returns the full URI of the CAS login endpoint to which a user must be
* redirected in order to authenticate and receive a CAS ticket.
*
* @return
* The full URI of the CAS login endpoint.
*
* @throws GuacamoleException
* If configuration information required for generating the login URI
* cannot be read.
*/
public URI getLoginURI() throws GuacamoleException {
return UriBuilder.fromUri(confService.getAuthorizationEndpoint())
.path(CAS_LOGIN_URI)
.queryParam("service", confService.getRedirectURI())
.build();
}
@Override
public void shutdown() {
// Nothing to clean up

View File

@@ -34,7 +34,8 @@ public class CASAuthenticationProvider extends SSOAuthenticationProvider {
* against an CAS service
*/
public CASAuthenticationProvider() {
super(AuthenticationProviderService.class, new CASAuthenticationProviderModule());
super(AuthenticationProviderService.class,
CASResource.class, new CASAuthenticationProviderModule());
}
@Override

View File

@@ -0,0 +1,43 @@
/*
* 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 javax.ws.rs.core.Response;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.auth.sso.SSOResource;
/**
* REST API resource that automatically redirects users to the CAS login
* endpoint.
*/
public class CASResource implements SSOResource {
/**
* Service for authenticating users using CAS.
*/
@Inject
private AuthenticationProviderService authService;
@Override
public Response redirectToIdentityProvider() throws GuacamoleException {
return Response.seeOther(authService.getLoginURI()).build();
}
}

View File

@@ -1,78 +0,0 @@
/*
* 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);
}
}

View File

@@ -9,6 +9,15 @@
"org.apache.guacamole.auth.cas.CASAuthenticationProvider"
],
"css" : [
"styles/sso-providers.css"
],
"html" : [
"html/sso-providers.html",
"html/sso-provider-cas.html"
],
"translations" : [
"translations/ca.json",
"translations/de.json",

View File

@@ -0,0 +1,4 @@
<meta name="after-children" content=".login-ui .sso-provider-list:last-child">
<li class="sso-provider sso-provider-cas"><a href="api/ext/cas/login">{{
'LOGIN.NAME_IDP_CAS' | translate
}}</a></li>