From 4dfb4b5c66ea3e6a89976260824f1d52aff9fffd Mon Sep 17 00:00:00 2001 From: David Bateman Date: Wed, 8 Apr 2026 19:52:11 +0200 Subject: [PATCH] Remove accidentally committed files --- .../AuthenticationProviderService.java.orig | 274 -------- ...penIDAuthenticationProviderModule.java.rej | 11 - .../conf/ConfigurationService.java.orig | 639 ------------------ .../token/TokenValidationService.java.orig | 430 ------------ 4 files changed, 1354 deletions(-) delete mode 100644 extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java.orig delete mode 100644 extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java.rej delete mode 100644 extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java.orig delete mode 100644 extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java.orig diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java.orig b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java.orig deleted file mode 100644 index 1513b9b18..000000000 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/AuthenticationProviderService.java.orig +++ /dev/null @@ -1,274 +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; - -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.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Set; -import javax.ws.rs.core.UriBuilder; -import org.apache.guacamole.auth.openid.conf.ConfigurationService; -import org.apache.guacamole.auth.openid.OpenIDAuthenticationSessionManager; -import org.apache.guacamole.auth.openid.token.TokenValidationService; -import org.apache.guacamole.auth.openid.util.PKCEUtil; -import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.auth.sso.NonceService; -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; -import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException; -import org.apache.guacamole.net.auth.IdentifierGenerator; -import org.jose4j.jwt.JwtClaims; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import org.apache.guacamole.net.auth.IdentifierGenerator; - -/** - * Service that authenticates Guacamole users by processing OpenID tokens. - */ -@Singleton -public class AuthenticationProviderService implements SSOAuthenticationProviderService { - - /** - * Logger for this class. - */ - private final Logger logger = LoggerFactory.getLogger(AuthenticationProviderService.class); - - /** - * The standard HTTP parameter which will be included within the URL by all - * OpenID services upon successful implicit flow authentication. - */ - public static final String IMPLICIT_TOKEN_PARAMETER_NAME = "id_token"; - - /** - * The standard HTTP parameter which will be included within the URL by all - * OpenID services upon successful code flow authentication. Used to recover - * the stored user state. - */ - public static final String CODE_TOKEN_PARAMETER_NAME = "code"; - - /** - * The name of the query parameter that identifies an active authentication - * session (in-progress OpenID authentication attempt). - */ - public static final String AUTH_SESSION_QUERY_PARAM = "state"; - - /** - * The REST API used for a local rediect and callback for the IdP to - * return a code and access stored PKCE verifiers - */ - public static final String CODE_REDIRECT_API = "/api/ext/openid/redirect"; - - /** - * Service for retrieving OpenID configuration information. - */ - @Inject - private ConfigurationService confService; - - /** - * Manager of active SAML authentication attempts. - */ - @Inject - private OpenIDAuthenticationSessionManager sessionManager; - - /** - * Service for validating and generating unique nonce values. - */ - @Inject - private NonceService nonceService; - - /** - * Service for validating received ID tokens. - */ - @Inject - private TokenValidationService tokenService; - - /** - * Provider for AuthenticatedUser objects. - */ - @Inject - private Provider authenticatedUserProvider; - - /** - * Return the value of the session identifier associated with the given - * credentials, or null if no session identifier is found in the - * credentials. - * - * @param credentials - * The credentials from which to extract the session identifier. - * - * @return - * The session identifier associated with the given credentials, or - * null if no identifier is found. - */ - public static String getSessionIdentifier(Credentials credentials) { - - // Return the session identifier from the request params, if set, or - // null otherwise - return credentials != null ? credentials.getParameter(AUTH_SESSION_QUERY_PARAM) : null; - } - - @Override - public SSOAuthenticatedUser authenticateUser(Credentials credentials) - throws GuacamoleException { - - String username = null; - Set groups = null; - Map tokens = Collections.emptyMap(); - - logger.debug("OpenID authentication with {} flow (ID: {}, Secret: {}, PKCE: {})", - confService.getFlowType(), - confService.getClientID(), - confService.getClientSecret(), - confService.isPKCERequired()); - - if (confService.getFlowType().equals("implicit")) { - String token = credentials.getParameter(IMPLICIT_TOKEN_PARAMETER_NAME); - if (token != null) { - JwtClaims claims = tokenService.validateToken(token); - if (claims != null) { - username = tokenService.processUsername(claims); - groups = tokenService.processGroups(claims); - tokens = tokenService.processAttributes(claims); - } - } - } else { - String verifier = null; - if (confService.isPKCERequired()) { - // Recover session - String identifier = getSessionIdentifier(credentials); - if (identifier != null) { - verifier = sessionManager.getVerifier(identifier); - } - } - String code = credentials.getParameter("code"); - if (code != null && (confService.isPKCERequired() == false || verifier != null)) { - JwtClaims claims = tokenService.validateCode(code, verifier); - if (claims != null) { - username = tokenService.processUsername(claims); - groups = tokenService.processGroups(claims); - tokens = tokenService.processAttributes(claims); - } - } - } - - // If the username was successfully retrieved from the token, produce - // authenticated user - if (username != null) { - // Create corresponding authenticated user - SSOAuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); - authenticatedUser.init(username, credentials, groups, tokens); - return authenticatedUser; - } - - // 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[] { - new RedirectField(AUTH_SESSION_QUERY_PARAM, getLoginURI(), - new TranslatableMessage("LOGIN.INFO_IDP_REDIRECT_PENDING")) - })) - ); - } - - @Override - public URI getLoginURI() throws GuacamoleException { - if (confService.getFlowType().equals("implicit")) { - return UriBuilder.fromUri(confService.getAuthorizationEndpoint()) - .queryParam("scope", confService.getScope()) - .queryParam("response_type", IMPLICIT_TOKEN_PARAMETER_NAME) - .queryParam("client_id", confService.getClientID()) - .queryParam("redirect_uri", confService.getRedirectURI()) - .queryParam("nonce", nonceService.generate(confService.getMaxNonceValidity() * 60000L)) - .build(); - } else { - UriBuilder builder = UriBuilder.fromUri(confService.getAuthorizationEndpoint()) - .queryParam("scope", confService.getScope()) - .queryParam("response_type", CODE_TOKEN_PARAMETER_NAME) - .queryParam("client_id", confService.getClientID()) - .queryParam("redirect_uri", confService.getRedirectURI()); - - if (confService.isPKCERequired()) { - String codeVerifier = PKCEUtil.generateCodeVerifier(); - String codeChallenge; - - try { - codeChallenge = PKCEUtil.generateCodeChallenge(codeVerifier); - } - catch (Exception e) { - throw new GuacamoleException("Unable to compute PKCE challenge", e); - } - - // Store verifier for authenticateUser - OpenIDAuthenticationSession session = new OpenIDAuthenticationSession(codeVerifier, - confService.getAuthenticationTimeout() * 60000L); - String identifier = IdentifierGenerator.generateIdentifier(); - sessionManager.defer(session, identifier); - - builder.queryParam("code_challenge", codeChallenge) - .queryParam("code_challenge_method", "S256") - .queryParam(AUTH_SESSION_QUERY_PARAM, identifier); - } - - return builder.build(); - } - } - - @Override - public URI getLogoutURI(String idToken) throws GuacamoleException { - - // If no logout endpoint is configured, return null - URI logoutEndpoint = confService.getLogoutEndpoint(); - if (logoutEndpoint == null) - return null; - - // Build the logout URI with appropriate parameters - UriBuilder logoutUriBuilder = UriBuilder.fromUri(logoutEndpoint); - - // Add post_logout_redirect_uri parameter - logoutUriBuilder.queryParam("post_logout_redirect_uri", - confService.getPostLogoutRedirectURI()); - - // Add id_token_hint if available, otherwise add client_id - if (idToken != null && !idToken.isEmpty()) - logoutUriBuilder.queryParam("id_token_hint", idToken); - else - logoutUriBuilder.queryParam("client_id", confService.getClientID()); - - return logoutUriBuilder.build(); - } - - @Override - public void shutdown() { - sessionManager.shutdown(); - } - -} diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java.rej b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java.rej deleted file mode 100644 index 588486f9b..000000000 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java.rej +++ /dev/null @@ -1,11 +0,0 @@ ---- extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java 2026-04-08 19:37:41.518619970 +0200 -+++ extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/OpenIDAuthenticationProviderModule.java 2026-04-08 16:43:54.617087120 +0200 -@@ -37,7 +37,7 @@ - * The configuration environment for this server and extension. - */ - private final Environment environment = new OpenIDEnvironment(); -- -+ - @Override - protected void configure() { - bind(ConfigurationService.class); diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java.orig b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java.orig deleted file mode 100644 index cf7bc1407..000000000 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/conf/ConfigurationService.java.orig +++ /dev/null @@ -1,639 +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.conf; - -import com.google.inject.Inject; -import java.net.URI; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.environment.Environment; -import org.apache.guacamole.properties.BooleanGuacamoleProperty; -import org.apache.guacamole.properties.IntegerGuacamoleProperty; -import org.apache.guacamole.properties.StringGuacamoleProperty; -import org.apache.guacamole.properties.URIGuacamoleProperty; - -/** - * Service for retrieving configuration information regarding the OpenID - * service. - */ -public class ConfigurationService { - - /** - * The default OICD flow type - */ - private static final String DEFAULT_FLOW_TYPE = "implicit"; - - /** - * The default claim type to use to retrieve an authenticated user's - * username. - */ - private static final String DEFAULT_USERNAME_CLAIM_TYPE = "email"; - - /** - * The default claim type to use to retrieve an authenticated user's - * groups. - */ - private static final String DEFAULT_GROUPS_CLAIM_TYPE = "groups"; - - /** - * The default JWT claims list to map to tokens. - */ - private static final List DEFAULT_ATTRIBUTES_CLAIM_TYPE = Collections.emptyList(); - - /** - * The default space-separated list of OpenID scopes to request. - */ - private static final String DEFAULT_SCOPE = "openid email profile"; - - /** - * The default amount of clock skew tolerated for timestamp comparisons - * between the Guacamole server and OpenID service clocks, in seconds. - */ - private static final int DEFAULT_ALLOWED_CLOCK_SKEW = 30; - - /** - * The default maximum amount of time that an OpenID token should remain - * valid, in minutes. - */ - private static final int DEFAULT_MAX_TOKEN_VALIDITY = 300; - - /** - * The default maximum amount of time that a nonce generated by the - * Guacamole server should remain valid, in minutes. - */ - private static final int DEFAULT_MAX_NONCE_VALIDITY = 10; - - /** - * The authorization endpoint (URI) of the OpenID service. - */ - private static final URIGuacamoleProperty OPENID_AUTHORIZATION_ENDPOINT = - new URIGuacamoleProperty() { - - @Override - public String getName() { return "openid-authorization-endpoint"; } - - }; - - /** - * The endpoint (URI) of the JWKS service which defines how received ID - * tokens (JWTs) shall be validated. - */ - private static final URIGuacamoleProperty OPENID_JWKS_ENDPOINT = - new URIGuacamoleProperty() { - - @Override - public String getName() { return "openid-jwks-endpoint"; } - - }; - - /** - * The issuer to expect for all received ID tokens. - */ - private static final StringGuacamoleProperty OPENID_ISSUER = - new StringGuacamoleProperty() { - - @Override - public String getName() { return "openid-issuer"; } - - }; - - /** - * The token endpoint (URI) of the OIDC service. - */ - private static final URIGuacamoleProperty OPENID_TOKEN_ENDPOINT = - new URIGuacamoleProperty() { - @Override - public String getName() { - return "openid-token-endpoint"; - } - }; - - /** - * The flow type of the OpenID service. - */ - private static final StringGuacamoleProperty OPENID_FLOW_TYPE = - new StringGuacamoleProperty() { - - @Override - public String getName() { return "openid-flow-type"; } - - }; - - /** - * OIDC client secret which should be submitted to the OIDC service when - * validating tokens with code flow - */ - private static final StringGuacamoleProperty OPENID_CLIENT_SECRET = - new StringGuacamoleProperty() { - @Override - public String getName() { - return "openid-client-secret"; - } - }; - - /** - * True if "Proof Key for Code Exchange" (PKCE) must be used. - */ - private static final BooleanGuacamoleProperty OPENID_PKCE_REQUIRED = - new BooleanGuacamoleProperty() { - @Override - public String getName() { - return "openid-pkce-required"; - } - }; - - /** - * The maximum amount of time to allow for an in-progress OpenID - * authentication attempt to be completed, in minutes. A user that takes - * longer than this amount of time to complete authentication with their - * identity provider will be redirected back to the identity provider to - * try again. - */ - private static final IntegerGuacamoleProperty OPENID_AUTH_TIMEOUT = - new IntegerGuacamoleProperty() { - @Override - public String getName() { return "openid-auth-timeout"; } - - }; - - - /** - * The claim type which contains the authenticated user's username within - * any valid JWT. - */ - private static final StringGuacamoleProperty OPENID_USERNAME_CLAIM_TYPE = - new StringGuacamoleProperty() { - - @Override - public String getName() { return "openid-username-claim-type"; } - - }; - - /** - * The claim type which contains the authenticated user's groups within - * any valid JWT. - */ - private static final StringGuacamoleProperty OPENID_GROUPS_CLAIM_TYPE = - new StringGuacamoleProperty() { - - @Override - public String getName() { return "openid-groups-claim-type"; } - - }; - - /** - * The claims within any valid JWT that should be mapped to - * the authenticated user's tokens, as configured with guacamole.properties. - */ - private static final StringGuacamoleProperty OPENID_ATTRIBUTES_CLAIM_TYPE = - new StringGuacamoleProperty() { - @Override - public String getName() { return "openid-attributes-claim-type"; } - }; - - /** - * The space-separated list of OpenID scopes to request. - */ - private static final StringGuacamoleProperty OPENID_SCOPE = - new StringGuacamoleProperty() { - - @Override - public String getName() { return "openid-scope"; } - - }; - - /** - * The amount of clock skew tolerated for timestamp comparisons between the - * Guacamole server and OpenID service clocks, in seconds. - */ - private static final IntegerGuacamoleProperty OPENID_ALLOWED_CLOCK_SKEW = - new IntegerGuacamoleProperty() { - - @Override - public String getName() { return "openid-allowed-clock-skew"; } - - }; - - /** - * The maximum amount of time that an OpenID token should remain valid, in - * minutes. - */ - private static final IntegerGuacamoleProperty OPENID_MAX_TOKEN_VALIDITY = - new IntegerGuacamoleProperty() { - - @Override - public String getName() { return "openid-max-token-validity"; } - - }; - - /** - * The maximum amount of time that a nonce generated by the Guacamole server - * should remain valid, in minutes. As each OpenID request has a unique - * nonce value, this imposes an upper limit on the amount of time any - * particular OpenID request can result in successful authentication within - * Guacamole. - */ - private static final IntegerGuacamoleProperty OPENID_MAX_NONCE_VALIDITY = - new IntegerGuacamoleProperty() { - - @Override - public String getName() { return "openid-max-nonce-validity"; } - - }; - - /** - * OpenID client ID which should be submitted to the OpenID service when - * necessary. This value is typically provided by the OpenID service when - * OpenID credentials are generated for your application. - */ - private static final StringGuacamoleProperty OPENID_CLIENT_ID = - new StringGuacamoleProperty() { - - @Override - public String getName() { return "openid-client-id"; } - - }; - - /** - * The URI that the OpenID 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. - */ - private static final URIGuacamoleProperty OPENID_REDIRECT_URI = - new URIGuacamoleProperty() { - - @Override - public String getName() { return "openid-redirect-uri"; } - - }; - - /** - * The logout endpoint (URI) of the OpenID service. If specified, users - * will be redirected to this endpoint when they log out, allowing them - * to log out from the OpenID provider as well. - */ - private static final URIGuacamoleProperty OPENID_LOGOUT_ENDPOINT = - new URIGuacamoleProperty() { - - @Override - public String getName() { return "openid-logout-endpoint"; } - - }; - - /** - * The URI that the OpenID service should redirect to after logout is - * complete. If not specified, the main redirect URI will be used. - */ - private static final URIGuacamoleProperty OPENID_POST_LOGOUT_REDIRECT_URI = - new URIGuacamoleProperty() { - - @Override - public String getName() { return "openid-post-logout-redirect-uri"; } - - }; - - /** - * The Guacamole server environment. - */ - @Inject - private Environment environment; - - /** - * Returns the authorization endpoint (URI) of the OpenID service as - * configured with guacamole.properties. - * - * @return - * The authorization endpoint of the OpenID 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(OPENID_AUTHORIZATION_ENDPOINT); - } - - /** - * Returns the OpenID client ID which should be submitted to the OpenID - * service when necessary, as configured with guacamole.properties. This - * value is typically provided by the OpenID service when OpenID credentials - * are generated for your application. - * - * @return - * The client ID to use when communicating with the OpenID service, - * as configured with guacamole.properties. - * - * @throws GuacamoleException - * If guacamole.properties cannot be parsed, or if the client ID - * property is missing. - */ - public String getClientID() throws GuacamoleException { - return environment.getRequiredProperty(OPENID_CLIENT_ID); - } - - /** - * Returns the URI that the OpenID 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 client secret to use when communicating with the OpenID service, - * as configured with 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(OPENID_REDIRECT_URI); - } - - /** - * Returns the issuer to expect for all received ID tokens, as configured - * with guacamole.properties. - * - * @return - * The issuer to expect for all received ID tokens, as configured with - * guacamole.properties. - * - * @throws GuacamoleException - * If guacamole.properties cannot be parsed, or if the issuer property - * is missing. - */ - public String getIssuer() throws GuacamoleException { - return environment.getRequiredProperty(OPENID_ISSUER); - } - - /** - * Returns the endpoint (URI) of the JWKS service which defines how - * received ID tokens (JWTs) shall be validated, as configured with - * guacamole.properties. - * - * @return - * The endpoint (URI) of the JWKS service which defines how received ID - * tokens (JWTs) shall be validated, as configured with - * guacamole.properties. - * - * @throws GuacamoleException - * If guacamole.properties cannot be parsed, or if the JWKS endpoint - * property is missing. - */ - public URI getJWKSEndpoint() throws GuacamoleException { - return environment.getRequiredProperty(OPENID_JWKS_ENDPOINT); - } - - /** - * Returns the token endpoint (URI) of the OIDC service as - * configured with guacamole.properties. - * - * @return - * The token endpoint of the OIDC service, as configured with - * guacamole.properties. - * - * @throws GuacamoleException - * If guacamole.properties cannot be parsed, or if the authorization - * endpoint property is missing. - */ - public URI getTokenEndpoint() throws GuacamoleException { - return environment.getRequiredProperty(OPENID_TOKEN_ENDPOINT); - } - - /** - * Returns the flow type of the OpenID service as configured with guacamole.properties. - * - * @return - * The flow type of the OpenID service, as configured with guacamole.properties. Can - * be either 'implicit' or 'code'. - * - * @throws GuacamoleException - * If guacamole.properties cannot be parsed. - */ - public String getFlowType() throws GuacamoleException { - return environment.getProperty(OPENID_FLOW_TYPE, DEFAULT_FLOW_TYPE); - } - - /** - * Returns the OIDC client secret used for toen validation, as configured - * with guacamole.properties. This value is typically provided by the OIDC - * service when OIDC credentials are generated for your application, and - * may be null. - * - * @return - * The client secret to use when communicating with the OIDC service, - * as configured with guacamole.properties. - * - * @throws GuacamoleException - * If guacamole.properties cannot be parsed, or if the client ID - * property is missing. - */ - public String getClientSecret() throws GuacamoleException { - return environment.getProperty(OPENID_CLIENT_SECRET); - } - - /** - * Returns a boolean value of whether "Proof Key for Code Exchange (PKCE)" - * will be used, as configured with guacamole.properties. The choice of - * whether to use PKCE is up to you, but the OIDC service must support - * it. By default will be false. - * - * @return - * The whether to use PKCE or not, as configured with guacamole.properties. - * - * @throws GuacamoleException - * If guacamole.properties cannot be parsed. - */ - public boolean isPKCERequired() throws GuacamoleException { - Boolean enabled = environment.getProperty(OPENID_PKCE_REQUIRED); - return enabled != null && enabled; - } - - /** - * Returns the maximum amount of time to allow for an in-progress OpenID - * authentication attempt to be completed, in minutes. A user that takes - * longer than this amount of time to complete authentication with their - * identity provider will be redirected back to the identity provider to - * try again. - * - * @return - * The maximum amount of time to allow for an in-progress SAML - * authentication attempt to be completed, in minutes. - * - * @throws GuacamoleException - * If the authentication timeout cannot be parsed. - */ - public int getAuthenticationTimeout() throws GuacamoleException { - return environment.getProperty(OPENID_AUTH_TIMEOUT, 5); - } - - - /** - * Returns the claim type which contains the authenticated user's username - * within any valid JWT, as configured with guacamole.properties. By - * default, this will be "email". - * - * @return - * The claim type which contains the authenticated user's username - * within any valid JWT, as configured with guacamole.properties. - * - * @throws GuacamoleException - * If guacamole.properties cannot be parsed. - */ - public String getUsernameClaimType() throws GuacamoleException { - return environment.getProperty(OPENID_USERNAME_CLAIM_TYPE, DEFAULT_USERNAME_CLAIM_TYPE); - } - - /** - * Returns the claim type which contains the authenticated user's groups - * within any valid JWT, as configured with guacamole.properties. By - * default, this will be "groups". - * - * @return - * The claim type which contains the authenticated user's groups - * within any valid JWT, as configured with guacamole.properties. - * - * @throws GuacamoleException - * If guacamole.properties cannot be parsed. - */ - public String getGroupsClaimType() throws GuacamoleException { - return environment.getProperty(OPENID_GROUPS_CLAIM_TYPE, DEFAULT_GROUPS_CLAIM_TYPE); - } - - /** - * Returns the claims list within any valid JWT that should be mapped to - * the authenticated user's tokens, as configured with guacamole.properties. - * Empty by default. - * - * @return - * The claims list within any valid JWT that should be mapped to - * the authenticated user's tokens, as configured with guacamole.properties. - * - * @throws GuacamoleException - * If guacamole.properties cannot be parsed. - */ - public Collection getAttributesClaimType() throws GuacamoleException { - return environment.getPropertyCollection(OPENID_ATTRIBUTES_CLAIM_TYPE, DEFAULT_ATTRIBUTES_CLAIM_TYPE); - } - - /** - * Returns the space-separated list of OpenID scopes to request. By default, - * this will be "openid email profile". The OpenID scopes determine the - * information returned within the OpenID token, and thus affect what - * values can be used as an authenticated user's username. - * - * @return - * The space-separated list of OpenID scopes to request when identifying - * a user. - * - * @throws GuacamoleException - * If guacamole.properties cannot be parsed. - */ - public String getScope() throws GuacamoleException { - return environment.getProperty(OPENID_SCOPE, DEFAULT_SCOPE); - } - - /** - * Returns the amount of clock skew tolerated for timestamp comparisons - * between the Guacamole server and OpenID service clocks, in seconds. Too - * much clock skew will affect token expiration calculations, possibly - * allowing old tokens to be used. By default, this will be 30. - * - * @return - * The amount of clock skew tolerated for timestamp comparisons, in - * seconds. - * - * @throws GuacamoleException - * If guacamole.properties cannot be parsed. - */ - public int getAllowedClockSkew() throws GuacamoleException { - return environment.getProperty(OPENID_ALLOWED_CLOCK_SKEW, DEFAULT_ALLOWED_CLOCK_SKEW); - } - - /** - * Returns the maximum amount of time that an OpenID token should remain - * valid, in minutes. A token received from an OpenID service which is - * older than this amount of time will be rejected, even if it is otherwise - * valid. By default, this will be 300 (5 hours). - * - * @return - * The maximum amount of time that an OpenID token should remain valid, - * in minutes. - * - * @throws GuacamoleException - * If guacamole.properties cannot be parsed. - */ - public int getMaxTokenValidity() throws GuacamoleException { - return environment.getProperty(OPENID_MAX_TOKEN_VALIDITY, DEFAULT_MAX_TOKEN_VALIDITY); - } - - /** - * Returns the maximum amount of time that a nonce generated by the - * Guacamole server should remain valid, in minutes. As each OpenID request - * has a unique nonce value, this imposes an upper limit on the amount of - * time any particular OpenID request can result in successful - * authentication within Guacamole. By default, this will be 10. - * - * @return - * The maximum amount of time that a nonce generated by the Guacamole - * server should remain valid, in minutes. - * - * @throws GuacamoleException - * If guacamole.properties cannot be parsed. - */ - public int getMaxNonceValidity() throws GuacamoleException { - return environment.getProperty(OPENID_MAX_NONCE_VALIDITY, DEFAULT_MAX_NONCE_VALIDITY); - } - - /** - * Returns the logout endpoint (URI) of the OpenID service, as configured - * with guacamole.properties. If configured, users will be redirected to - * this endpoint when they log out from Guacamole. - * - * @return - * The logout endpoint of the OpenID service, as configured with - * guacamole.properties, or null if not configured. - * - * @throws GuacamoleException - * If guacamole.properties cannot be parsed. - */ - public URI getLogoutEndpoint() throws GuacamoleException { - return environment.getProperty(OPENID_LOGOUT_ENDPOINT); - } - - /** - * Returns the URI that the OpenID service should redirect to after logout - * is complete, as configured with guacamole.properties. If not configured, - * the main redirect URI will be used. - * - * @return - * The post-logout redirect URI, as configured with - * guacamole.properties, or the main redirect URI if not specified. - * - * @throws GuacamoleException - * If guacamole.properties cannot be parsed. - */ - public URI getPostLogoutRedirectURI() throws GuacamoleException { - return environment.getProperty(OPENID_POST_LOGOUT_REDIRECT_URI, getRedirectURI()); - } - -} diff --git a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java.orig b/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java.orig deleted file mode 100644 index fd9f4c16a..000000000 --- a/extensions/guacamole-auth-sso/modules/guacamole-auth-sso-openid/src/main/java/org/apache/guacamole/auth/openid/token/TokenValidationService.java.orig +++ /dev/null @@ -1,430 +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.token; - -import com.google.inject.Inject; -import java.io.BufferedReader; -import java.io.OutputStream; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.core.UriBuilder; -import org.apache.guacamole.GuacamoleException; -import org.apache.guacamole.auth.openid.conf.ConfigurationService; -import org.apache.guacamole.auth.sso.NonceService; -import org.apache.guacamole.token.TokenName; -import org.jose4j.json.JsonUtil; -import org.jose4j.jwk.HttpsJwks; -import org.jose4j.jwt.JwtClaims; -import org.jose4j.jwt.MalformedClaimException; -import org.jose4j.jwt.consumer.InvalidJwtException; -import org.jose4j.jwt.consumer.JwtConsumer; -import org.jose4j.jwt.consumer.JwtConsumerBuilder; -import org.jose4j.keys.resolvers.HttpsJwksVerificationKeyResolver; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Service for validating ID tokens forwarded to us by the client, verifying - * that they did indeed come from the OpenID service. - */ -public class TokenValidationService { - - /** - * Logger for this class. - */ - private final Logger logger = LoggerFactory.getLogger(TokenValidationService.class); - - /** - * The prefix to use when generating token names. - */ - public static final String OIDC_ATTRIBUTE_TOKEN_PREFIX = "OIDC_"; - - /** - * Service for retrieving OpenID configuration information. - */ - @Inject - private ConfigurationService confService; - - /** - * Service for validating and generating unique nonce values. - */ - @Inject - private NonceService nonceService; - - /** - * Validates the given ID token, using implicit flow, returning the JwtClaims - * contained therein. If the ID token is invalid, null is returned. - * - * @param token - * The ID token to validate. - * - * @return - * The JWT claims contained within the given ID token if it passes tests, - * or null if the token is not valid. - * - * @throws GuacamoleException - * If guacamole.properties could not be parsed. - */ - public JwtClaims validateToken(String token) throws GuacamoleException { - // Validating the token requires a JWKS key resolver - HttpsJwks jwks = new HttpsJwks(confService.getJWKSEndpoint().toString()); - HttpsJwksVerificationKeyResolver resolver = new HttpsJwksVerificationKeyResolver(jwks); - - // Create JWT consumer for validating received token - JwtConsumer jwtConsumer = new JwtConsumerBuilder() - .setRequireExpirationTime() - .setMaxFutureValidityInMinutes(confService.getMaxTokenValidity()) - .setAllowedClockSkewInSeconds(confService.getAllowedClockSkew()) - .setRequireSubject() - .setExpectedIssuer(confService.getIssuer()) - .setExpectedAudience(confService.getClientID()) - .setVerificationKeyResolver(resolver) - .build(); - - try { - // Validate JWT - JwtClaims claims = jwtConsumer.processToClaims(token); - - // Verify a nonce is present - String nonce = claims.getStringClaimValue("nonce"); - if (nonce != null) { - // Verify that we actually generated the nonce, and that it has not - // already been used - if (nonceService.isValid(nonce)) { - // nonce is valid, consider claims valid - return claims; - } - else { - logger.info("Rejected OpenID token with invalid/old nonce."); - } - } - else { - logger.info("Rejected OpenID token without nonce."); - } - } - // Log any failures to validate/parse the JWT - catch (MalformedClaimException e) { - logger.info("Rejected OpenID token with malformed claim: {}", e.getMessage(), e); - } - catch (InvalidJwtException e) { - logger.info("Rejected invalid OpenID token: {}", e.getMessage(), e); - } - - return null; - } - - /** - * Validates the given ID token, using code flow, returning the JwtClaims - * contained therein. If the ID token is invalid, null is returned. - * - * @param code - * The code to validate and receive the id_token. - * - * @param verifier - * A PKCE verifier or null if not used. - * - * @return - * The JWT claims contained within the given ID token if it passes tests, - * or null if the token is not valid. - * - * @throws GuacamoleException - * If guacamole.properties could not be parsed. - */ - public JwtClaims validateCode(String code, String verifier) throws GuacamoleException { - // Validating the token requires a JWKS key resolver - HttpsJwks jwks = new HttpsJwks(confService.getJWKSEndpoint().toString()); - HttpsJwksVerificationKeyResolver resolver = new HttpsJwksVerificationKeyResolver(jwks); - - /* Exchange code → token */ - String token = exchangeCode(code, verifier); - - // Create JWT consumer for validating received token - JwtConsumer jwtConsumer = new JwtConsumerBuilder() - .setRequireExpirationTime() - .setMaxFutureValidityInMinutes(confService.getMaxTokenValidity()) - .setAllowedClockSkewInSeconds(confService.getAllowedClockSkew()) - .setRequireSubject() - .setExpectedIssuer(confService.getIssuer()) - .setExpectedAudience(confService.getClientID()) - .setVerificationKeyResolver(resolver) - .build(); - - try { - // Validate JWT - return jwtConsumer.processToClaims(token); - } - // Log any failures to validate/parse the JWT - catch (InvalidJwtException e) { - logger.info("Rejected invalid OpenID token: {}", e.getMessage(), e); - } - - return null; - } - - /** - * URLEncodes a key/value pair - * - * @param key - * The key to encode - * - * @param value - * The value to encode - * - * @return - * The urlencoded kay/value pair - */ - private String urlencode(String key, String value) { - StringBuilder builder = new StringBuilder(); - return builder.append(URLEncoder.encode(key, StandardCharsets.UTF_8)) - .append("=") - .append(URLEncoder.encode(value, StandardCharsets.UTF_8)) - .toString(); - } - - /** - * Exchanges the authorization code for tokens. - * - * @param code - * The authorization code received from the IdP. - * @param codeVerifier - * The PKCE verifier (or null if PKCE is disabled). - * - * @return - * The token string returned. - * - * @throws GuacamoleException - * If a valid token is not returned. - */ - private String exchangeCode(String code, String verifier) throws GuacamoleException { - - try { - StringBuilder bodyBuilder = new StringBuilder(); - bodyBuilder.append(urlencode("grant_type", "authorization_code")).append("&"); - bodyBuilder.append(urlencode("code", code)).append("&"); - bodyBuilder.append(urlencode("redirect_uri", confService.getRedirectURI().toString())).append("&"); - bodyBuilder.append(urlencode("scope", confService.getScope())).append("&"); - bodyBuilder.append(urlencode("client_id", confService.getClientID())); - - String clientSecret = confService.getClientSecret(); - if (clientSecret != null && !clientSecret.trim().isEmpty()) { - bodyBuilder.append("&").append(urlencode("client_secret", clientSecret)); - } - - if (confService.isPKCERequired()) { - bodyBuilder.append("&").append(urlencode("code_verifier", verifier)); - } - - // Build the final URI and convert to a URL - URL url = confService.getTokenEndpoint().toURL(); - - // Open connection, using HttpURLConnection - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - - conn.setRequestMethod("POST"); - conn.setDoOutput(true); - conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8" - ); - - try (OutputStream out = conn.getOutputStream()) { - byte [] body = bodyBuilder.toString().getBytes(StandardCharsets.UTF_8); - out.write(body, 0, body.length); - } - - // Read response - int status = conn.getResponseCode(); - - BufferedReader reader = new BufferedReader( - new InputStreamReader( - status >= 200 && status < 300 - ? conn.getInputStream() - : conn.getErrorStream(), - StandardCharsets.UTF_8 - ) - ); - - StringBuilder responseBody = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - responseBody.append(line); - } - reader.close(); - - Map json = JsonUtil.parseJson(responseBody.toString()); - - if (status < 200 || status >= 300) { - throw new GuacamoleException("Token endpoint error (" + status + "): " + json.toString()); - } - - return (String) json.get("id_token"); - - } catch (Exception e) { - logger.info("Rejected invalid OpenID code exchange: {}", e.getMessage(), e); - } - return null; - } - - /** - * Parses the given JwtClaims, returning the username contained - * therein, as defined by the username claim type given in - * guacamole.properties. If the username claim type is missing or - * is invalid, null is returned. - * - * @param claims - * A valid JwtClaims to extract the username from. - * - * @return - * The username contained within the given JwtClaims, or null if the - * claim is not valid or the username claim type is missing, - * - * @throws GuacamoleException - * If guacamole.properties could not be parsed. - */ - public String processUsername(JwtClaims claims) throws GuacamoleException { - String usernameClaim = confService.getUsernameClaimType(); - - if (claims != null) { - try { - // Pull username from claims - String username = claims.getStringClaimValue(usernameClaim); - if (username != null) - return username; - } - catch (MalformedClaimException e) { - logger.info("Rejected OpenID token with malformed claim: {}", e.getMessage(), e); - } - - // Warn if username was not present in token, as it likely means - // the system is not set up correctly - logger.warn("Username claim \"{}\" missing from token. Perhaps the " - + "OpenID scope and/or username claim type are " - + "misconfigured?", usernameClaim); - } - - // Could not retrieve username from JWT - return null; - } - - /** - * Parses the given JwtClaims, returning the groups contained - * therein, as defined by the groups claim type given in - * guacamole.properties. If the groups claim type is missing or - * is invalid, an empty set is returned. - * - * @param claims - * A valid JwtClaims to extract groups from. - * - * @return - * A Set of String representing the groups the user is member of - * from the OpenID provider point of view, or an empty Set if - * claim is not valid or the groups claim type is missing, - * - * @throws GuacamoleException - * If guacamole.properties could not be parsed. - */ - public Set processGroups(JwtClaims claims) throws GuacamoleException { - String groupsClaim = confService.getGroupsClaimType(); - - if (claims != null) { - try { - // Pull groups from claims - List oidcGroups = claims.getStringListClaimValue(groupsClaim); - if (oidcGroups != null && !oidcGroups.isEmpty()) - return Collections.unmodifiableSet(new HashSet<>(oidcGroups)); - } - catch (MalformedClaimException e) { - logger.info("Rejected OpenID token with malformed claim: {}", e.getMessage(), e); - } - } - - // Could not retrieve groups from JWT - return Collections.emptySet(); - } - - /** - * Parses the given JwtClaims, returning the attributes contained - * therein, as defined by the attributes claim type given in - * guacamole.properties. If the attributes claim type is missing or - * is invalid, an empty set is returned. - * - * @param claims - * A valid JwtClaims to extract attributes from. - * - * @return - * A Map of String,String representing the attributes and values - * from the OpenID provider point of view, or an empty Map if - * claim is not valid or the attributes claim type is missing. - * - * @throws GuacamoleException - * If guacamole.properties could not be parsed. - */ - public Map processAttributes(JwtClaims claims) throws GuacamoleException { - Collection attributesClaim = confService.getAttributesClaimType(); - - if (claims != null && !attributesClaim.isEmpty()) { - try { - logger.debug("Iterating over attributes claim list : {}", attributesClaim); - - // We suppose all claims are resolved, so the hashmap is initialised to - // the size of the configuration list - Map tokens = new HashMap(attributesClaim.size()); - - // We iterate over the configured attributes - for (String key: attributesClaim) { - // Retrieve the corresponding claim - String oidcAttr = claims.getStringClaimValue(key); - - // We do have a matching claim and it is not empty - if (oidcAttr != null && !oidcAttr.isEmpty()) { - // append the prefixed claim value to the token map with its value - String tokenName = TokenName.canonicalize(key, OIDC_ATTRIBUTE_TOKEN_PREFIX); - tokens.put(tokenName, oidcAttr); - logger.debug("Claim {} found and set to {}", key, tokenName); - } - else { - // wanted attribute is not found in the claim - logger.debug("Claim {} not found in JWT.", key); - } - } - - // We did process all the expected claims - return Collections.unmodifiableMap(tokens); - } - catch (MalformedClaimException e) { - logger.info("Rejected OpenID token with malformed claim: {}", e.getMessage(), e); - } - } - - // Could not retrieve attributes from JWT - logger.debug("Attributes claim not defined. Returning empty map."); - return Collections.emptyMap(); - } -}