GUACAMOLE-210: Validate the JWT using jose.4.j.
Project: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/commit/d27ba444 Tree: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/tree/d27ba444 Diff: http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/diff/d27ba444 Branch: refs/heads/master Commit: d27ba44439e702964cb668886ccbc35f740b38e8 Parents: fdc0313 Author: Michael Jumper <[email protected]> Authored: Sun Jun 12 23:03:47 2016 -0700 Committer: Michael Jumper <[email protected]> Committed: Mon Sep 25 13:06:43 2017 -0700 ---------------------------------------------------------------------- extensions/guacamole-auth-openid/pom.xml | 7 ++ .../oauth/AuthenticationProviderService.java | 13 ++- .../OAuthAuthenticationProviderModule.java | 2 + .../auth/oauth/conf/ConfigurationService.java | 52 +++++++++- .../oauth/conf/OAuthGuacamoleProperties.java | 35 +++++++ .../oauth/token/TokenValidationService.java | 102 +++++++++++++++++++ 6 files changed, 207 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/d27ba444/extensions/guacamole-auth-openid/pom.xml ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-openid/pom.xml b/extensions/guacamole-auth-openid/pom.xml index 60691e2..fa819c8 100644 --- a/extensions/guacamole-auth-openid/pom.xml +++ b/extensions/guacamole-auth-openid/pom.xml @@ -86,6 +86,13 @@ <scope>provided</scope> </dependency> + <!-- Java implementation of JOSE (jose.4.j) --> + <dependency> + <groupId>org.bitbucket.b_c</groupId> + <artifactId>jose4j</artifactId> + <version>0.5.1</version> + </dependency> + <!-- Guice --> <dependency> <groupId>com.google.inject</groupId> http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/d27ba444/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/AuthenticationProviderService.java ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/AuthenticationProviderService.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/AuthenticationProviderService.java index 0aac968..d89f087 100644 --- a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/AuthenticationProviderService.java +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/AuthenticationProviderService.java @@ -23,9 +23,10 @@ import com.google.inject.Inject; import com.google.inject.Provider; import java.util.Arrays; import javax.servlet.http.HttpServletRequest; -import org.apache.guacamole.auth.oauth.user.AuthenticatedUser; import org.apache.guacamole.auth.oauth.conf.ConfigurationService; import org.apache.guacamole.auth.oauth.form.OAuthTokenField; +import org.apache.guacamole.auth.oauth.token.TokenValidationService; +import org.apache.guacamole.auth.oauth.user.AuthenticatedUser; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.form.Field; import org.apache.guacamole.net.auth.Credentials; @@ -52,6 +53,12 @@ public class AuthenticationProviderService { private ConfigurationService confService; /** + * Service for validating received ID tokens. + */ + @Inject + private TokenValidationService tokenService; + + /** * Provider for AuthenticatedUser objects. */ @Inject @@ -82,12 +89,12 @@ public class AuthenticationProviderService { if (request != null) token = request.getParameter(OAuthTokenField.PARAMETER_NAME); - // TODO: Actually validate received token + // If token provided, validate and produce authenticated user if (token != null) { // Create corresponding authenticated user AuthenticatedUser authenticatedUser = authenticatedUserProvider.get(); - authenticatedUser.init("STUB", credentials); + authenticatedUser.init(tokenService.processUsername(token), credentials); return authenticatedUser; } http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/d27ba444/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/OAuthAuthenticationProviderModule.java ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/OAuthAuthenticationProviderModule.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/OAuthAuthenticationProviderModule.java index 202e6a2..f838063 100644 --- a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/OAuthAuthenticationProviderModule.java +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/OAuthAuthenticationProviderModule.java @@ -21,6 +21,7 @@ package org.apache.guacamole.auth.oauth; import com.google.inject.AbstractModule; import org.apache.guacamole.auth.oauth.conf.ConfigurationService; +import org.apache.guacamole.auth.oauth.token.TokenValidationService; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.environment.Environment; import org.apache.guacamole.environment.LocalEnvironment; @@ -73,6 +74,7 @@ public class OAuthAuthenticationProviderModule extends AbstractModule { // Bind OAuth-specific services bind(ConfigurationService.class); + bind(TokenValidationService.class); } http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/d27ba444/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/conf/ConfigurationService.java ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/conf/ConfigurationService.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/conf/ConfigurationService.java index 9debab7..1304d58 100644 --- a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/conf/ConfigurationService.java +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/conf/ConfigurationService.java @@ -79,11 +79,61 @@ public class ConfigurationService { * as configured with guacamole.properties. * * @throws GuacamoleException - * If guacamole.properties cannot be parsed, or if the client secret + * If guacamole.properties cannot be parsed, or if the redirect URI * property is missing. */ public String getRedirectURI() throws GuacamoleException { return environment.getRequiredProperty(OAuthGuacamoleProperties.OAUTH_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(OAuthGuacamoleProperties.OAUTH_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 String getJWKSEndpoint() throws GuacamoleException { + return environment.getRequiredProperty(OAuthGuacamoleProperties.OAUTH_JWKS_ENDPOINT); + } + + /** + * Returns the claim type which contains the authenticated user's username + * within any valid JWT, as configured with guacamole.properties. + * + * @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, or if the username claim + * type property is missing. + */ + public String getUsernameClaimType() throws GuacamoleException { + return environment.getRequiredProperty(OAuthGuacamoleProperties.OAUTH_USERNAME_CLAIM_TYPE); + } + } http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/d27ba444/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/conf/OAuthGuacamoleProperties.java ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/conf/OAuthGuacamoleProperties.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/conf/OAuthGuacamoleProperties.java index 34952fe..cfb4eb3 100644 --- a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/conf/OAuthGuacamoleProperties.java +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/conf/OAuthGuacamoleProperties.java @@ -45,6 +45,41 @@ public class OAuthGuacamoleProperties { }; /** + * The endpoint (URI) of the JWKS service which defines how received ID + * tokens (JWTs) shall be validated. + */ + public static final StringGuacamoleProperty OAUTH_JWKS_ENDPOINT = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "oauth-jwks-endpoint"; } + + }; + + /** + * The issuer to expect for all received ID tokens. + */ + public static final StringGuacamoleProperty OAUTH_ISSUER = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "oauth-issuer"; } + + }; + + /** + * The claim type which contains the authenticated user's username within + * any valid JWT. + */ + public static final StringGuacamoleProperty OAUTH_USERNAME_CLAIM_TYPE = + new StringGuacamoleProperty() { + + @Override + public String getName() { return "oauth-username-claim-type"; } + + }; + + /** * OAuth client ID which should be submitted to the OAuth service when * necessary. This value is typically provided by the OAuth service when * OAuth credentials are generated for your application. http://git-wip-us.apache.org/repos/asf/incubator-guacamole-client/blob/d27ba444/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/token/TokenValidationService.java ---------------------------------------------------------------------- diff --git a/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/token/TokenValidationService.java b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/token/TokenValidationService.java new file mode 100644 index 0000000..a61f7ce --- /dev/null +++ b/extensions/guacamole-auth-openid/src/main/java/org/apache/guacamole/auth/oauth/token/TokenValidationService.java @@ -0,0 +1,102 @@ +/* + * 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.oauth.token; + +import com.google.inject.Inject; +import org.apache.guacamole.auth.oauth.conf.ConfigurationService; +import org.apache.guacamole.GuacamoleException; +import org.apache.guacamole.GuacamoleSecurityException; +import org.apache.guacamole.GuacamoleServerException; +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; + +/** + * Service for validating ID tokens forwarded to us by the client, verifying + * that they did indeed come from the OAuth service. + */ +public class TokenValidationService { + + @Inject + private ConfigurationService confService; + + /** + * Validates and parses the given ID token, returning the username contained + * therein, as defined by the username claim type given in + * guacamole.properties. If the username claim type is missing or the ID + * token is invalid, an exception is thrown instead. + * + * @param token + * The ID token to validate and parse. + * + * @return + * The username contained within the given ID token. + * + * @throws GuacamoleException + * If the ID token is not valid, the username claim type is missing, or + * guacamole.properties could not be parsed. + */ + public String processUsername(String token) throws GuacamoleException { + + // Validating the token requires a JWKS key resolver + HttpsJwks jwks = new HttpsJwks(confService.getJWKSEndpoint()); + HttpsJwksVerificationKeyResolver resolver = new HttpsJwksVerificationKeyResolver(jwks); + + // Create JWT consumer for validating received token + JwtConsumer jwtConsumer = new JwtConsumerBuilder() + .setRequireExpirationTime() + .setMaxFutureValidityInMinutes(300) + .setAllowedClockSkewInSeconds(30) + .setRequireSubject() + .setExpectedIssuer(confService.getIssuer()) + .setExpectedAudience(confService.getClientID()) + .setVerificationKeyResolver(resolver) + .build(); + + try { + + // Validate JWT + JwtClaims claims = jwtConsumer.processToClaims(token); + + // Pull username from claims + String username = claims.getStringClaimValue(confService.getUsernameClaimType()); + if (username == null) + throw new GuacamoleSecurityException("Username missing from token"); + + // Username successfully retrieved from the JWT + return username; + + } + + // Rethrow any failures to validate/parse the JWT + catch (InvalidJwtException e) { + throw new GuacamoleSecurityException("Invalid ID token.", e); + } + catch (MalformedClaimException e) { + throw new GuacamoleServerException("Unable to parse JWT claims.", e); + } + + } + +}
