From 0e9860ecf75691db9bd45cf52e04d96e45310892 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Thu, 25 Nov 2021 19:35:46 -0800 Subject: [PATCH] GUACAMOLE-1364: Allow both traditional username/password and SSO. --- .../guacamole-auth-sso-base/.ratignore | 1 + .../modules/guacamole-auth-sso-base/pom.xml | 6 ++ .../auth/sso/SSOAuthenticationProvider.java | 20 ++++- .../guacamole/auth/sso/SSOResource.java | 49 +++++++++++ .../main/resources/html/sso-providers.html | 7 ++ .../main/resources/styles/sso-providers.css | 50 +++++++++++ .../src/main/resources/translations/en.json | 12 ++- .../modules/guacamole-auth-sso-cas/.ratignore | 1 + .../cas/AuthenticationProviderService.java | 49 ++++++++--- .../auth/cas/CASAuthenticationProvider.java | 3 +- .../guacamole/auth/cas/CASResource.java | 43 +++++++++ .../auth/cas/form/CASTicketField.java | 78 ----------------- .../src/main/resources/guac-manifest.json | 9 ++ .../main/resources/html/sso-provider-cas.html | 4 + .../guacamole-auth-sso-openid/.ratignore | 1 + .../openid/AuthenticationProviderService.java | 52 +++++++---- .../openid/OpenIDAuthenticationProvider.java | 3 +- .../guacamole/auth/openid/OpenIDResource.java | 43 +++++++++ .../auth/openid/form/TokenField.java | 87 ------------------- .../src/main/resources/guac-manifest.json | 9 ++ .../resources/html/sso-provider-openid.html | 4 + .../saml/AuthenticationProviderService.java | 6 +- .../auth/saml/SAMLAuthenticationProvider.java | 10 +-- .../acs/AssertionConsumerServiceResource.java | 8 +- .../src/main/resources/guac-manifest.json | 9 ++ .../resources/html/sso-provider-saml.html | 4 + 26 files changed, 360 insertions(+), 208 deletions(-) create mode 100644 extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOResource.java create mode 100644 extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/resources/html/sso-providers.html create mode 100644 extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/resources/styles/sso-providers.css create mode 100644 extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/CASResource.java delete mode 100644 extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/form/CASTicketField.java create mode 100644 extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/resources/html/sso-provider-cas.html create mode 100644 extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDResource.java delete mode 100644 extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/form/TokenField.java create mode 100644 extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/resources/html/sso-provider-openid.html create mode 100644 extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/resources/html/sso-provider-saml.html diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/.ratignore b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/.ratignore index e69de29bb..da318d12f 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/.ratignore +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/.ratignore @@ -0,0 +1 @@ +src/main/resources/html/*.html diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/pom.xml b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/pom.xml index b23280596..11724ecf4 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/pom.xml +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/pom.xml @@ -50,6 +50,12 @@ guice + + + javax.ws.rs + jsr311-api + + diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOAuthenticationProvider.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOAuthenticationProvider.java index 1a389283f..e6846a059 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOAuthenticationProvider.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOAuthenticationProvider.java @@ -64,14 +64,20 @@ public abstract class SSOAuthenticationProvider extends AbstractAuthenticationPr * The SSOAuthenticationProviderService implementation that should be * used for core authentication functions. * + * @param ssoResource + * The SSOResource that should be used to manually redirect the user to + * the IdP, as well as to provide any implementation-specific REST + * endpoints. + * * @param modules * Any additional modules that should be used when creating the Guice * injector. */ public SSOAuthenticationProvider( Class authService, + Class ssoResource, Module... modules) { - this(authService, Arrays.asList(modules)); + this(authService, ssoResource, Arrays.asList(modules)); } /** @@ -86,12 +92,18 @@ public abstract class SSOAuthenticationProvider extends AbstractAuthenticationPr * The SSOAuthenticationProviderService implementation that should be * used for core authentication functions. * + * @param ssoResource + * The SSOResource that should be used to manually redirect the user to + * the IdP, as well as to provide any implementation-specific REST + * endpoints. + * * @param modules * Any additional modules that should be used when creating the Guice * injector. */ public SSOAuthenticationProvider( Class authService, + Class ssoResource, Iterable modules) { injector = Guice.createInjector(Iterables.concat(Collections.singletonList(new AbstractModule() { @@ -100,6 +112,7 @@ public abstract class SSOAuthenticationProvider extends AbstractAuthenticationPr bind(AuthenticationProvider.class).toInstance(SSOAuthenticationProvider.this); bind(Environment.class).toInstance(LocalEnvironment.getInstance()); bind(SSOAuthenticationProviderService.class).to(authService); + bind(SSOResource.class).to(ssoResource); } }), modules)); @@ -145,6 +158,11 @@ public abstract class SSOAuthenticationProvider extends AbstractAuthenticationPr } + @Override + public SSOResource getResource() { + return getInjector().getInstance(SSOResource.class); + } + @Override public void shutdown() { injector.getInstance(SSOAuthenticationProviderService.class).shutdown(); diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOResource.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOResource.java new file mode 100644 index 000000000..ce61db0b1 --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/java/org/apache/guacamole/auth/sso/SSOResource.java @@ -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.sso; + +import javax.ws.rs.core.Response; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import org.apache.guacamole.GuacamoleException; + +/** + * REST API resource that provides allows the user to be manually redirected to + * the applicable identity provider. Implementations may also provide + * additional resources and endpoints beneath this resource as needed. + */ +public interface SSOResource { + + /** + * Redirects the user to the relevant identity provider. If the SSO + * extension defining this resource is not the primary extension, and thus + * the user will not be automatically redirected to the IdP, this endpoint + * allows that redirect to occur manually upon a link/button click. + * + * @return + * An HTTP Response that will redirect the user to the IdP. + * + * @throws GuacamoleException + * If an error occurs preventing the redirect from being created. + */ + @GET + @Path("login") + public Response redirectToIdentityProvider() throws GuacamoleException; + +} diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/resources/html/sso-providers.html b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/resources/html/sso-providers.html new file mode 100644 index 000000000..bac5f6e33 --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/resources/html/sso-providers.html @@ -0,0 +1,7 @@ + +
+
+ {{ 'LOGIN.SECTION_HEADER_SSO_OPTIONS' | translate }} +
    +
    +
    diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/resources/styles/sso-providers.css b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/resources/styles/sso-providers.css new file mode 100644 index 000000000..27eae1ea7 --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/resources/styles/sso-providers.css @@ -0,0 +1,50 @@ +/* + * 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. + */ + +.login-ui .sso-providers { + display: none; +} + +.login-ui .sso-providers:last-child { + display: table-row; +} + +.sso-providers ul { + list-style: none; +} + +.sso-providers ul, .sso-providers li { + display: inline-block; + margin: 0; + padding: 0; +} + +.sso-providers li::before { + content: ' / '; +} + +.sso-providers li:first-child::before { + display: none; +} + +.sso-providers-content { + display: table-cell; + padding: 0.25em 0.5em; + height: 1px; +} diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/resources/translations/en.json b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/resources/translations/en.json index 47fc299fa..859301568 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/resources/translations/en.json +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-base/src/main/resources/translations/en.json @@ -13,10 +13,14 @@ }, "LOGIN" : { - "FIELD_HEADER_ID_TOKEN" : "", - "FIELD_HEADER_STATE" : "", - "FIELD_HEADER_TICKET" : "", - "INFO_IDP_REDIRECT_PENDING" : "Please wait, redirecting to identity provider..." + "FIELD_HEADER_ID_TOKEN" : "", + "FIELD_HEADER_STATE" : "", + "FIELD_HEADER_TICKET" : "", + "INFO_IDP_REDIRECT_PENDING" : "Please wait, redirecting to identity provider...", + "NAME_IDP_CAS" : "CAS", + "NAME_IDP_OPENID" : "OpenID", + "NAME_IDP_SAML" : "SAML", + "SECTION_HEADER_SSO_OPTIONS" : "Sign in with:" } } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/.ratignore b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/.ratignore index e69de29bb..da318d12f 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/.ratignore +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/.ratignore @@ -0,0 +1 @@ +src/main/resources/html/*.html diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java index 7aa3c1b87..62eec8a64 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/AuthenticationProviderService.java @@ -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 diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/CASAuthenticationProvider.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/CASAuthenticationProvider.java index dda5b73b5..225b230d0 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/CASAuthenticationProvider.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/CASAuthenticationProvider.java @@ -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 diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/CASResource.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/CASResource.java new file mode 100644 index 000000000..74079f985 --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/CASResource.java @@ -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(); + } + +} diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/form/CASTicketField.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/form/CASTicketField.java deleted file mode 100644 index a925dfcc3..000000000 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/java/org/apache/guacamole/auth/cas/form/CASTicketField.java +++ /dev/null @@ -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); - - } - -} diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/resources/guac-manifest.json index a2aaa9433..25584cf89 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/resources/guac-manifest.json +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/resources/guac-manifest.json @@ -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", diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/resources/html/sso-provider-cas.html b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/resources/html/sso-provider-cas.html new file mode 100644 index 000000000..348da10fa --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-cas/src/main/resources/html/sso-provider-cas.html @@ -0,0 +1,4 @@ + +
  • {{ + 'LOGIN.NAME_IDP_CAS' | translate +}}
  • diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/.ratignore b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/.ratignore index e69de29bb..da318d12f 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/.ratignore +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/.ratignore @@ -0,0 +1 @@ +src/main/resources/html/*.html diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java index 08ad373dd..3320d5b1f 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java @@ -21,18 +21,21 @@ package org.apache.guacamole.auth.openid; import com.google.inject.Inject; import com.google.inject.Provider; +import com.google.inject.Singleton; +import java.net.URI; import java.util.Arrays; import java.util.Collections; import java.util.Set; import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.UriBuilder; import org.apache.guacamole.auth.openid.conf.ConfigurationService; -import org.apache.guacamole.auth.openid.form.TokenField; import org.apache.guacamole.auth.openid.token.NonceService; import org.apache.guacamole.auth.openid.token.TokenValidationService; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.sso.SSOAuthenticationProviderService; import org.apache.guacamole.auth.sso.user.SSOAuthenticatedUser; import org.apache.guacamole.form.Field; +import org.apache.guacamole.form.RedirectField; import org.apache.guacamole.language.TranslatableMessage; import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.credentials.CredentialsInfo; @@ -42,8 +45,15 @@ import org.jose4j.jwt.JwtClaims; /** * Service that authenticates Guacamole users by processing OpenID tokens. */ +@Singleton public class AuthenticationProviderService implements SSOAuthenticationProviderService { + /** + * The standard HTTP parameter which will be included within the URL by all + * OpenID services upon successful authentication and redirect. + */ + public static final String TOKEN_PARAMETER_NAME = "id_token"; + /** * Service for retrieving OpenID configuration information. */ @@ -78,7 +88,7 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS // Validate OpenID token in request, if present, and derive username HttpServletRequest request = credentials.getRequest(); if (request != null) { - String token = request.getParameter(TokenField.PARAMETER_NAME); + String token = request.getParameter(TOKEN_PARAMETER_NAME); if (token != null) { JwtClaims claims = tokenService.validateToken(token); if (claims != null) { @@ -99,26 +109,38 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS } - // Request OpenID token + // Request OpenID token (will automatically redirect the user to the + // OpenID authorization page via JavaScript) throw new GuacamoleInvalidCredentialsException("Invalid login.", new CredentialsInfo(Arrays.asList(new Field[] { - - // OpenID-specific token (will automatically redirect the user - // to the authorization page via JavaScript) - new TokenField( - confService.getAuthorizationEndpoint(), - confService.getScope(), - confService.getClientID(), - confService.getRedirectURI(), - nonceService.generate(confService.getMaxNonceValidity() * 60000L), - new TranslatableMessage("LOGIN.INFO_IDP_REDIRECT_PENDING") - ) - + new RedirectField(TOKEN_PARAMETER_NAME, getLoginURI(), + new TranslatableMessage("LOGIN.INFO_IDP_REDIRECT_PENDING")) })) ); } + /** + * Returns the full URI of the OpenID login endpoint to which a user must + * be redirected in order to authenticate and receive an OpenID token. + * + * @return + * The full URI of the OpenID login endpoint, including unique nonce. + * + * @throws GuacamoleException + * If configuration information required for generating the login URI + * cannot be read. + */ + public URI getLoginURI() throws GuacamoleException { + return UriBuilder.fromUri(confService.getAuthorizationEndpoint()) + .queryParam("scope", confService.getScope()) + .queryParam("response_type", "id_token") + .queryParam("client_id", confService.getClientID()) + .queryParam("redirect_uri", confService.getRedirectURI()) + .queryParam("nonce", nonceService.generate(confService.getMaxNonceValidity() * 60000L)) + .build(); + } + @Override public void shutdown() { // Nothing to clean up diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProvider.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProvider.java index 32588d811..49b7da86f 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProvider.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProvider.java @@ -34,7 +34,8 @@ public class OpenIDAuthenticationProvider extends SSOAuthenticationProvider { * against an OpenID service. */ public OpenIDAuthenticationProvider() { - super(AuthenticationProviderService.class, new OpenIDAuthenticationProviderModule()); + super(AuthenticationProviderService.class, OpenIDResource.class, + new OpenIDAuthenticationProviderModule()); } @Override diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDResource.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDResource.java new file mode 100644 index 000000000..e47cf4c17 --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDResource.java @@ -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.openid; + +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 OpenID identity + * provider. + */ +public class OpenIDResource implements SSOResource { + + /** + * Service for authenticating users using OpenID. + */ + @Inject + private AuthenticationProviderService authService; + + @Override + public Response redirectToIdentityProvider() throws GuacamoleException { + return Response.seeOther(authService.getLoginURI()).build(); + } + +} diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/form/TokenField.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/form/TokenField.java deleted file mode 100644 index 44d90a8fa..000000000 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/form/TokenField.java +++ /dev/null @@ -1,87 +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.openid.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 token returned by an OpenID Connect - * service. - */ -public class TokenField extends RedirectField { - - /** - * The standard HTTP parameter which will be included within the URL by all - * OpenID services upon successful authentication and redirect. - */ - public static final String PARAMETER_NAME = "id_token"; - - /** - * Creates a new field which requests authentication via OpenID connect. - * Successful authentication at the OpenID Connect service will result in - * the client being redirected to the specified redirect URI. The OpenID - * token 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 OpenID authentication - * requests. - * - * @param scope - * The space-delimited list of OpenID scopes to request from the - * identity provider, such as "openid" or "openid email profile". - * - * @param clientID - * The ID of the OpenID client. This is normally determined ahead of - * time by the OpenID service through some manual credential request - * procedure. - * - * @param redirectURI - * The URI that the OpenID service should redirect to upon successful - * authentication. - * - * @param nonce - * A random string unique to this request. To defend against replay - * attacks, this value must cease being valid after its first use. - * - * @param redirectMessage - * The message that will be displayed to the user during redirect. This - * will be processed through Guacamole's translation system. - */ - public TokenField(URI authorizationEndpoint, String scope, - String clientID, URI redirectURI, String nonce, - TranslatableMessage redirectMessage) { - - super(PARAMETER_NAME, UriBuilder.fromUri(authorizationEndpoint) - .queryParam("scope", scope) - .queryParam("response_type", "id_token") - .queryParam("client_id", clientID) - .queryParam("redirect_uri", redirectURI) - .queryParam("nonce", nonce) - .build(), - redirectMessage); - - } - -} diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/resources/guac-manifest.json index b48bd2d31..3d96ab9de 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/resources/guac-manifest.json +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/resources/guac-manifest.json @@ -9,6 +9,15 @@ "org.apache.guacamole.auth.openid.OpenIDAuthenticationProvider" ], + "css" : [ + "styles/sso-providers.css" + ], + + "html" : [ + "html/sso-providers.html", + "html/sso-provider-openid.html" + ], + "translations" : [ "translations/ca.json", "translations/de.json", diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/resources/html/sso-provider-openid.html b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/resources/html/sso-provider-openid.html new file mode 100644 index 000000000..0da260f96 --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/resources/html/sso-provider-openid.html @@ -0,0 +1,4 @@ + +
  • {{ + 'LOGIN.NAME_IDP_OPENID' | translate +}}
  • diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/AuthenticationProviderService.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/AuthenticationProviderService.java index bb4f368ff..5a1e15881 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/AuthenticationProviderService.java @@ -21,6 +21,7 @@ package org.apache.guacamole.auth.saml; import com.google.inject.Inject; import com.google.inject.Provider; +import com.google.inject.Singleton; import java.net.URI; import java.util.Arrays; import javax.servlet.http.HttpServletRequest; @@ -35,12 +36,13 @@ import org.apache.guacamole.form.RedirectField; import org.apache.guacamole.language.TranslatableMessage; import org.apache.guacamole.net.auth.Credentials; import org.apache.guacamole.net.auth.credentials.CredentialsInfo; -import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException; +import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException; /** * Service that authenticates Guacamole users by processing the responses of * SAML identity providers. */ +@Singleton public class AuthenticationProviderService implements SSOAuthenticationProviderService { /** @@ -94,7 +96,7 @@ public class AuthenticationProviderService implements SSOAuthenticationProviderS // Redirect to SAML IdP if no SAML identity is associated with the // Guacamole authentication request URI authUri = saml.createRequest(); - throw new GuacamoleInsufficientCredentialsException("Redirecting to SAML IdP.", + throw new GuacamoleInvalidCredentialsException("Redirecting to SAML IdP.", new CredentialsInfo(Arrays.asList(new Field[] { new RedirectField(AUTH_SESSION_QUERY_PARAM, authUri, new TranslatableMessage("LOGIN.INFO_IDP_REDIRECT_PENDING")) diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProvider.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProvider.java index 4d1f0b9d8..e356b36f6 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProvider.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/SAMLAuthenticationProvider.java @@ -19,7 +19,6 @@ package org.apache.guacamole.auth.saml; -import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.saml.acs.AssertionConsumerServiceResource; import org.apache.guacamole.auth.sso.SSOAuthenticationProvider; @@ -36,7 +35,9 @@ public class SAMLAuthenticationProvider extends SSOAuthenticationProvider { * against a SAML IdP. */ public SAMLAuthenticationProvider() { - super(AuthenticationProviderService.class, new SAMLAuthenticationProviderModule()); + super(AuthenticationProviderService.class, + AssertionConsumerServiceResource.class, + new SAMLAuthenticationProviderModule()); } @Override @@ -44,9 +45,4 @@ public class SAMLAuthenticationProvider extends SSOAuthenticationProvider { return "saml"; } - @Override - public Object getResource() throws GuacamoleException { - return getInjector().getInstance(AssertionConsumerServiceResource.class); - } - } diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/acs/AssertionConsumerServiceResource.java b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/acs/AssertionConsumerServiceResource.java index ba99d75fb..5c75feeca 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/acs/AssertionConsumerServiceResource.java +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/java/org/apache/guacamole/auth/saml/acs/AssertionConsumerServiceResource.java @@ -30,6 +30,7 @@ import javax.ws.rs.core.UriBuilder; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.auth.saml.AuthenticationProviderService; import org.apache.guacamole.auth.saml.conf.ConfigurationService; +import org.apache.guacamole.auth.sso.SSOResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,7 +39,7 @@ import org.slf4j.LoggerFactory; * endpoint. SAML identity providers will issue an HTTP POST to this endpoint * asserting the user's identity when the user has successfully authenticated. */ -public class AssertionConsumerServiceResource { +public class AssertionConsumerServiceResource implements SSOResource { /** * Logger for this class. @@ -63,6 +64,11 @@ public class AssertionConsumerServiceResource { @Inject private SAMLService saml; + @Override + public Response redirectToIdentityProvider() throws GuacamoleException { + return Response.seeOther(saml.createRequest()).build(); + } + /** * Processes the SAML response submitted by the SAML IdP via an HTTP POST. * If SSO has been successful, the user is redirected back to Guacamole to diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/resources/guac-manifest.json b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/resources/guac-manifest.json index ae1a67571..4dafda62a 100644 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/resources/guac-manifest.json +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/resources/guac-manifest.json @@ -9,6 +9,15 @@ "org.apache.guacamole.auth.saml.SAMLAuthenticationProvider" ], + "css" : [ + "styles/sso-providers.css" + ], + + "html" : [ + "html/sso-providers.html", + "html/sso-provider-saml.html" + ], + "translations" : [ "translations/ca.json", "translations/de.json", diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/resources/html/sso-provider-saml.html b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/resources/html/sso-provider-saml.html new file mode 100644 index 000000000..93a770434 --- /dev/null +++ b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-saml/src/main/resources/html/sso-provider-saml.html @@ -0,0 +1,4 @@ + +
  • {{ + 'LOGIN.NAME_IDP_SAML' | translate +}}