Add .gitignore and .ratignore files for various directories
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
gyurix
2025-04-29 21:43:12 +02:00
parent 983ecbfc53
commit be9f66dee9
2167 changed files with 254128 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
*~
target/
src/main/resources/generated/

View File

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

View File

@@ -0,0 +1,132 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-auth-sso-openid</artifactId>
<packaging>jar</packaging>
<version>1.6.0</version>
<name>guacamole-auth-sso-openid</name>
<url>http://guacamole.apache.org/</url>
<parent>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-auth-sso</artifactId>
<version>1.6.0</version>
<relativePath>../../</relativePath>
</parent>
<build>
<plugins>
<!-- JS/CSS Minification Plugin -->
<plugin>
<groupId>com.github.buckelieg</groupId>
<artifactId>minify-maven-plugin</artifactId>
<executions>
<execution>
<id>default-cli</id>
<configuration>
<charset>UTF-8</charset>
<webappSourceDir>${basedir}/src/main/resources</webappSourceDir>
<webappTargetDir>${project.build.directory}/classes</webappTargetDir>
<jsSourceDir>/</jsSourceDir>
<jsTargetDir>/</jsTargetDir>
<jsFinalFile>openid.js</jsFinalFile>
<jsSourceFiles>
<jsSourceFile>license.txt</jsSourceFile>
</jsSourceFiles>
<jsSourceIncludes>
<jsSourceInclude>**/*.js</jsSourceInclude>
</jsSourceIncludes>
<!-- Do not minify and include tests -->
<jsSourceExcludes>
<jsSourceExclude>**/*.test.js</jsSourceExclude>
</jsSourceExcludes>
<jsEngine>CLOSURE</jsEngine>
<!-- Disable warnings for JSDoc annotations -->
<closureWarningLevels>
<misplacedTypeAnnotation>OFF</misplacedTypeAnnotation>
<nonStandardJsDocs>OFF</nonStandardJsDocs>
</closureWarningLevels>
</configuration>
<goals>
<goal>minify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<!-- Guacamole Extension API -->
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-ext</artifactId>
</dependency>
<!-- Core SSO support -->
<dependency>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-auth-sso-base</artifactId>
</dependency>
<!-- Java implementation of JOSE (jose.4.j) -->
<dependency>
<groupId>org.bitbucket.b_c</groupId>
<artifactId>jose4j</artifactId>
<version>0.9.6</version>
</dependency>
<!-- Guice -->
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
</dependency>
<!-- Java servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</dependency>
<!-- JAX-RS Annotations -->
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,138 @@
/*
* 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.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.token.TokenValidationService;
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.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.
*/
@Inject
private ConfigurationService confService;
/**
* 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<SSOAuthenticatedUser> authenticatedUserProvider;
@Override
public SSOAuthenticatedUser authenticateUser(Credentials credentials)
throws GuacamoleException {
String username = null;
Set<String> groups = null;
Map<String,String> tokens = Collections.emptyMap();
// Validate OpenID token in request, if present, and derive username
String token = credentials.getParameter(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);
}
}
// 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(TOKEN_PARAMETER_NAME, getLoginURI(),
new TranslatableMessage("LOGIN.INFO_IDP_REDIRECT_PENDING"))
}))
);
}
@Override
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
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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 org.apache.guacamole.auth.sso.SSOAuthenticationProvider;
import org.apache.guacamole.auth.sso.SSOResource;
/**
* Guacamole authentication backend which authenticates users using an
* arbitrary external system implementing OpenID. No storage for connections is
* provided - only authentication. Storage must be provided by some other
* extension.
*/
public class OpenIDAuthenticationProvider extends SSOAuthenticationProvider {
/**
* Creates a new OpenIDAuthenticationProvider that authenticates users
* against an OpenID service.
*/
public OpenIDAuthenticationProvider() {
super(AuthenticationProviderService.class, SSOResource.class,
new OpenIDAuthenticationProviderModule());
}
@Override
public String getIdentifier() {
return "openid";
}
}

View File

@@ -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.openid;
import com.google.inject.AbstractModule;
import com.google.inject.Scopes;
import org.apache.guacamole.auth.openid.conf.ConfigurationService;
import org.apache.guacamole.auth.openid.conf.OpenIDEnvironment;
import org.apache.guacamole.auth.sso.NonceService;
import org.apache.guacamole.auth.openid.token.TokenValidationService;
import org.apache.guacamole.environment.Environment;
/**
* Guice module which configures OpenID-specific injections.
*/
public class OpenIDAuthenticationProviderModule extends AbstractModule {
/**
* The configuration environment for this server and extension.
*/
private final Environment environment = new OpenIDEnvironment();
@Override
protected void configure() {
bind(ConfigurationService.class);
bind(NonceService.class).in(Scopes.SINGLETON);
bind(TokenValidationService.class);
bind(Environment.class).toInstance(environment);
}
}

View File

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

View File

@@ -0,0 +1,39 @@
/*
* 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 org.apache.guacamole.environment.DelegatingEnvironment;
import org.apache.guacamole.environment.LocalEnvironment;
/**
* An environment for retrieving OpenID-related properties from the Guacamole
* configuration.
*/
public class OpenIDEnvironment extends DelegatingEnvironment {
/**
* Create a new instance of the configuration environment for the
* OpenID SSO module, pulling the default instance of the LocalEnvironment.
*/
public OpenIDEnvironment() {
super(LocalEnvironment.getInstance());
}
}

View File

@@ -0,0 +1,274 @@
/*
* 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.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 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.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, 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());
logger.debug("Malformed claim within received JWT.", e);
}
catch (InvalidJwtException e) {
logger.info("Rejected invalid OpenID token: {}", e.getMessage());
logger.debug("Invalid JWT received.", 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());
logger.debug("Malformed claim within received JWT.", 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<String> processGroups(JwtClaims claims) throws GuacamoleException {
String groupsClaim = confService.getGroupsClaimType();
if (claims != null) {
try {
// Pull groups from claims
List<String> 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());
logger.debug("Malformed claim within received JWT.", 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<String, String> processAttributes(JwtClaims claims) throws GuacamoleException {
Collection<String> 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<String, String> tokens = new HashMap<String, String>(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());
logger.debug("Malformed claim within received JWT.", e);
}
}
// Could not retrieve attributes from JWT
logger.debug("Attributes claim not defined. Returning empty map.");
return Collections.emptyMap();
}
}

View File

@@ -0,0 +1,38 @@
{
"guacamoleVersion" : "1.6.0",
"name" : "OpenID Authentication Extension",
"namespace" : "openid",
"authProviders" : [
"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",
"translations/en.json",
"translations/fr.json",
"translations/ja.json",
"translations/ko.json",
"translations/pl.json",
"translations/pt.json",
"translations/ru.json",
"translations/zh.json"
],
"js" : [
"openid.min.js"
]
}

View File

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

View File

@@ -0,0 +1,18 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

View File

@@ -0,0 +1,35 @@
/*
* 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.
*/
/**
* Before AngularJS routing takes effect, reformat the URL fragment
* from the format used by OpenID Connect ("#param1=value1&param2=value2&...")
* to the format used by AngularJS ("#/?param1=value1&param2=value2&...") such
* that the client side of Guacamole's authentication system will automatically
* forward the "id_token" value for server-side validation.
*
* Note that not all OpenID identity providers will include the "id_token"
* parameter in the first position; it may occur after several other parameters
* within the fragment.
*/
(function guacOpenIDTransformToken() {
if (/^#(?![?\/])(.*&)?id_token=/.test(location.hash))
location.hash = '/?' + location.hash.substring(1);
})();