Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GUACAMOLE-1844 : OIDC JWT claims as user token #943

Merged
merged 1 commit into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.core.UriBuilder;
Expand Down Expand Up @@ -84,6 +85,7 @@ public SSOAuthenticatedUser authenticateUser(Credentials credentials)

String username = null;
Set<String> groups = null;
Map<String,String> tokens = Collections.emptyMap();

// Validate OpenID token in request, if present, and derive username
HttpServletRequest request = credentials.getRequest();
Expand All @@ -94,6 +96,7 @@ public SSOAuthenticatedUser authenticateUser(Credentials credentials)
if (claims != null) {
username = tokenService.processUsername(claims);
groups = tokenService.processGroups(claims);
tokens = tokenService.processAttributes(claims);
}
}
}
Expand All @@ -104,7 +107,7 @@ public SSOAuthenticatedUser authenticateUser(Credentials credentials)

// Create corresponding authenticated user
SSOAuthenticatedUser authenticatedUser = authenticatedUserProvider.get();
authenticatedUser.init(username, credentials, groups, Collections.emptyMap());
authenticatedUser.init(username, credentials, groups, tokens);
return authenticatedUser;

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@

import com.google.inject.Inject;
import java.net.URI;
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.StringListProperty;
import org.apache.guacamole.properties.URIGuacamoleProperty;

/**
Expand All @@ -45,6 +48,11 @@ public class ConfigurationService {
*/
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.
*/
Expand Down Expand Up @@ -126,6 +134,16 @@ public class ConfigurationService {

};

/**
* The claims within any valid JWT that should be mapped to
* the authenticated user's tokens, as configured with guacamole.properties.
*/
private static final StringListProperty OPENID_ATTRIBUTES_CLAIM_TYPE =
new StringListProperty() {
@Override
public String getName() { return "openid-attributes-claim-type"; }
};

/**
* The space-separated list of OpenID scopes to request.
*/
Expand Down Expand Up @@ -326,6 +344,22 @@ 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 List<String> getAttributesClaimType() throws GuacamoleException {
return environment.getProperty(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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@

import com.google.inject.Inject;
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.auth.openid.conf.ConfigurationService;
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;
Expand All @@ -48,6 +51,11 @@ public class TokenValidationService {
*/
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.
*/
Expand Down Expand Up @@ -202,4 +210,64 @@ public Set<String> processGroups(JwtClaims claims) throws GuacamoleException {
// 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 {
List<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();
}
}
Loading