Skip to content

Commit

Permalink
Merge branch '1.x' into 2.x
Browse files Browse the repository at this point in the history
  • Loading branch information
fjuma committed Jun 14, 2023
2 parents e1d2e04 + c463e52 commit 4d83de9
Show file tree
Hide file tree
Showing 10 changed files with 474 additions and 5 deletions.
20 changes: 20 additions & 0 deletions http/oidc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@
<artifactId>slf4j-jboss-logmanager</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.minidev</groupId>
<artifactId>json-smart</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.htmlunit</groupId>
<artifactId>htmlunit</artifactId>
Expand All @@ -153,6 +158,21 @@
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class AccessToken extends JsonWebToken {
private static final String ALLOWED_ORIGINS = "allowed-origins";
private static final String REALM_ACCESS = "realm_access";
private static final String RESOURCE_ACCESS = "resource_access";
private static final String ROLES = "roles";
private static final String TRUSTED_CERTS = "trusted-certs";

/**
Expand Down Expand Up @@ -105,4 +106,13 @@ public RealmAccessClaim getResourceAccessClaim(String resource) {
public List<String> getTrustedCertsClaim() {
return getStringListClaimValue(TRUSTED_CERTS);
}

/**
* Get the roles claim.
*
* @return the roles claim
*/
public List<String> getRolesClaim() {
return getStringListClaimValue(ROLES);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public class Oidc {
static final String KEYCLOAK_CLIENT_CLUSTER_HOST = "client_cluster_host";
static final String KEYCLOAK_QUERY_BEARER_TOKEN = "k_query_bearer_token";
static final String DEFAULT_TOKEN_SIGNATURE_ALGORITHM = "RS256";
public static final String DISABLE_TYP_CLAIM_VALIDATION_PROPERTY_NAME = "wildfly.elytron.oidc.disable.typ.claim.validation";


// keycloak-specific request parameter used to specify the identifier of the identity provider that should be used to authenticate a user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.security.Principal;
import java.security.spec.AlgorithmParameterSpec;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.wildfly.security.auth.SupportLevel;
Expand Down Expand Up @@ -121,6 +122,14 @@ private static Set<String> getRolesFromSecurityContext(RefreshableOidcSecurityCo
roles.addAll(realmAccessClaim.getRoles());
}
}
// include roles from the standard "roles" claim if present
List<String> rolesClaim = accessToken.getRolesClaim();
if (! rolesClaim.isEmpty()) {
if (log.isTraceEnabled()) {
log.trace("use roles claim");
}
roles.addAll(rolesClaim);
}
if (log.isTraceEnabled()) {
log.trace("Setting roles: ");
for (String role : roles) {
Expand All @@ -129,4 +138,4 @@ private static Set<String> getRolesFromSecurityContext(RefreshableOidcSecurityCo
}
return roles;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,18 @@

import static org.wildfly.security.http.oidc.ElytronMessages.log;
import static org.wildfly.security.http.oidc.IDToken.AT_HASH;
import static org.wildfly.security.http.oidc.Oidc.DISABLE_TYP_CLAIM_VALIDATION_PROPERTY_NAME;
import static org.wildfly.security.http.oidc.Oidc.INVALID_AT_HASH_CLAIM;
import static org.wildfly.security.http.oidc.Oidc.INVALID_ISSUED_FOR_CLAIM;
import static org.wildfly.security.http.oidc.Oidc.INVALID_TYPE_CLAIM;
import static org.wildfly.security.http.oidc.Oidc.getJavaAlgorithmForHash;
import static org.wildfly.security.jose.jwk.JWKUtil.BASE64_URL;

import java.nio.charset.StandardCharsets;
import java.security.AccessController;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedAction;
import java.util.Arrays;

import javax.crypto.SecretKey;
Expand All @@ -52,6 +55,17 @@
*/
public class TokenValidator {

static final boolean DISABLE_TYP_CLAIM_VALIDATION_PROPERTY;

static {
DISABLE_TYP_CLAIM_VALIDATION_PROPERTY = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return Boolean.parseBoolean(System.getProperty(DISABLE_TYP_CLAIM_VALIDATION_PROPERTY_NAME, "false"));
}
});
}

private static final int HEADER_INDEX = 0;
private JwtConsumerBuilder jwtConsumerBuilder;
private OidcClientConfiguration clientConfiguration;
Expand Down Expand Up @@ -99,7 +113,9 @@ public AccessToken parseAndVerifyToken(final String bearerToken) throws OidcExce
try {
JwtContext jwtContext = setVerificationKey(bearerToken, jwtConsumerBuilder);
jwtConsumerBuilder.setRequireSubject();
jwtConsumerBuilder.registerValidator(new TypeValidator("Bearer"));
if (! DISABLE_TYP_CLAIM_VALIDATION_PROPERTY) {
jwtConsumerBuilder.registerValidator(new TypeValidator("Bearer"));
}
if (clientConfiguration.isVerifyTokenAudience()) {
jwtConsumerBuilder.setExpectedAudience(clientConfiguration.getResourceName());
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import org.jboss.resteasy.plugins.server.embedded.SimplePrincipal;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.InvalidJwtException;
import org.junit.Before;
import org.junit.Test;
import org.wildfly.security.auth.SupportLevel;
Expand Down Expand Up @@ -71,8 +72,8 @@ public void testGetRealmIdentityWithNonOidcPrincipal() throws RealmUnavailableEx
@Test
public void testGetRealmIdentityNoRoles() throws RealmUnavailableException {
// setup
RefreshableOidcSecurityContext securityContext = new RefreshableOidcSecurityContext();
securityContext.setCurrentRequestInfo(new OidcClientConfiguration(), null);
RefreshableOidcSecurityContext securityContext = new RefreshableOidcSecurityContext(new OidcClientConfiguration(),
null, null, new AccessToken(new JwtClaims()), null, null, null);
OidcPrincipal principal = new OidcPrincipal("john", securityContext);

// test
Expand Down Expand Up @@ -103,6 +104,7 @@ public void testGetRealmIdentityRolesCombined() throws RealmUnavailableException
final Map<String, Object> resourceAccess = new HashMap<>();
resourceAccess.put("SomeResource", createRoles("roleA"));
resourceAccess.put("SpecialResource", createRoles("roleB", "roleC"));
// standard roles claim not present
jwtClaims.setClaim("resource_access", resourceAccess);
jwtClaims.setClaim("realm_access", createRoles("roleC", "roleD"));

Expand Down Expand Up @@ -131,6 +133,7 @@ public void testGetRealmIdentityOnlyRealmRoles() throws RealmUnavailableExceptio
final JwtClaims jwtClaims = new JwtClaims();
final Map<String, Object> resourceAccess = new HashMap<>();
resourceAccess.put("SpecialResource", createRoles("roleB", "roleC"));
// standard roles claim not present
jwtClaims.setClaim("resource_access", resourceAccess);
jwtClaims.setClaim("realm_access", createRoles("roleC", "roleD"));

Expand Down Expand Up @@ -158,6 +161,7 @@ public void testGetRealmIdentityOnlyResourceRoles() throws RealmUnavailableExcep
final JwtClaims jwtClaims = new JwtClaims();
final Map<String, Object> resourceAccess = new HashMap<>();
resourceAccess.put("SpecialResource", createRoles("roleB", "roleC"));
// standard roles claim not present
jwtClaims.setClaim("resource_access", resourceAccess);
jwtClaims.setClaim("", new RealmAccessClaim(createRoles("roleC", "roleD")));

Expand Down Expand Up @@ -201,6 +205,119 @@ public void testGetRealmIdentityNoMappings() throws RealmUnavailableException {
assertTrue(roles.isEmpty());
}

@Test
public void testRolesWithEmptyRolesClaim() throws Exception {
OidcClientConfiguration clientConfiguration = new OidcClientConfiguration();
clientConfiguration.setClientId("rolesClient");

// use only realm role mappings
clientConfiguration.setUseRealmRoleMappings(true);
clientConfiguration.setUseResourceRoleMappings(false);
JwtClaims jwtClaims = populateBasicJwtClaims(true); // empty roles claim
Attributes.Entry roles = getRealmIdentityRoles(clientConfiguration, jwtClaims);

assertEquals(2, roles.size());
assertTrue(roles.contains("roleC"));
assertTrue(roles.contains("roleD"));

// use only resource role mappings
clientConfiguration.setUseRealmRoleMappings(false);
clientConfiguration.setUseResourceRoleMappings(true);
jwtClaims = populateBasicJwtClaims(true); // empty roles claim
roles = getRealmIdentityRoles(clientConfiguration, jwtClaims);

assertEquals(2, roles.size());
assertTrue(roles.contains("roleB"));
assertTrue(roles.contains("roleC"));

// use both realm role mappings and resource role mappings
clientConfiguration.setUseRealmRoleMappings(true);
clientConfiguration.setUseResourceRoleMappings(true);
jwtClaims = populateBasicJwtClaims(true); // empty roles claim
roles = getRealmIdentityRoles(clientConfiguration, jwtClaims);

assertEquals(3, roles.size());
assertTrue(roles.contains("roleB"));
assertTrue(roles.contains("roleC"));
assertTrue(roles.contains("roleD"));

// neither realm role mappings nor resource role mappings are included in the token
clientConfiguration.setUseRealmRoleMappings(true);
clientConfiguration.setUseResourceRoleMappings(true);
jwtClaims = populateBasicJwtClaims(true, false); // empty roles claim
roles = getRealmIdentityRoles(clientConfiguration, jwtClaims);
assertEquals(0, roles.size());
}

@Test
public void testRolesWithNonEmptyRolesClaim() throws Exception {
OidcClientConfiguration clientConfiguration = new OidcClientConfiguration();
clientConfiguration.setClientId("rolesClient");

// use only the standard roles claim
clientConfiguration.setUseRealmRoleMappings(false);
clientConfiguration.setUseResourceRoleMappings(false);
JwtClaims jwtClaims = populateBasicJwtClaims(false); // non-empty roles claim
Attributes.Entry roles = getRealmIdentityRoles(clientConfiguration, jwtClaims);

assertEquals(2, roles.size());
assertTrue(roles.contains("roleE"));
assertTrue(roles.contains("roleF"));

// use realm role mappings and the standard roles claim
clientConfiguration = new OidcClientConfiguration();
clientConfiguration.setClientId("rolesClient");
clientConfiguration.setUseRealmRoleMappings(true);
clientConfiguration.setUseResourceRoleMappings(false);
jwtClaims = populateBasicJwtClaims(false); // non-empty roles claim
roles = getRealmIdentityRoles(clientConfiguration, jwtClaims);

assertEquals(4, roles.size());
assertTrue(roles.contains("roleC"));
assertTrue(roles.contains("roleD"));
assertTrue(roles.contains("roleE"));
assertTrue(roles.contains("roleF"));

// use resource role mappings and the standard roles claim
clientConfiguration = new OidcClientConfiguration();
clientConfiguration.setClientId("rolesClient");
clientConfiguration.setUseRealmRoleMappings(false);
clientConfiguration.setUseResourceRoleMappings(true);
jwtClaims = populateBasicJwtClaims(false); // non-empty roles claim
roles = getRealmIdentityRoles(clientConfiguration, jwtClaims);

assertEquals(4, roles.size());
assertTrue(roles.contains("roleB"));
assertTrue(roles.contains("roleC"));
assertTrue(roles.contains("roleE"));
assertTrue(roles.contains("roleF"));

// use realm role mappings, resource role mappings, and the standard roles claim
clientConfiguration = new OidcClientConfiguration();
clientConfiguration.setClientId("rolesClient");
clientConfiguration.setUseRealmRoleMappings(true);
clientConfiguration.setUseResourceRoleMappings(true);
jwtClaims = populateBasicJwtClaims(false); // non-empty roles claim
roles = getRealmIdentityRoles(clientConfiguration, jwtClaims);

assertEquals(5, roles.size());
assertTrue(roles.contains("roleB"));
assertTrue(roles.contains("roleC"));
assertTrue(roles.contains("roleD"));
assertTrue(roles.contains("roleE"));
assertTrue(roles.contains("roleF"));

// neither realm role mappings nor resource role mappings are included in the token
clientConfiguration.setUseRealmRoleMappings(true);
clientConfiguration.setUseResourceRoleMappings(true);
jwtClaims = populateBasicJwtClaims(false, false); // non-empty-roles
roles = getRealmIdentityRoles(clientConfiguration, jwtClaims);

assertEquals(2, roles.size());
assertTrue(roles.contains("roleE"));
assertTrue(roles.contains("roleF"));
}

static Map<String, Object> createRoles(String... roleNames) {
final ArrayList<String> value = new ArrayList<>();
for (String role : roleNames) {
Expand All @@ -210,4 +327,76 @@ static Map<String, Object> createRoles(String... roleNames) {
roles.put("roles", value);
return roles;
}

private static JwtClaims populateBasicJwtClaims(boolean useEmptyRoles) throws InvalidJwtException {
return populateBasicJwtClaims(useEmptyRoles, true);
}

private static JwtClaims populateBasicJwtClaims(boolean useEmptyRoles, boolean includeRealmAndResourcesRoles) throws InvalidJwtException {
return JwtClaims.parse("{\n" +
" \"exp\": 1686249550,\n" +
" \"iat\": 1686249490,\n" +
" \"auth_time\": 1686249477,\n" +
" \"jti\": \"8c883880-e9ec-4e96-a2d2-ee32460e0d6c\",\n" +
" \"iss\": \"http://localhost:8080/realms/master\",\n" +
" \"aud\": \"account\",\n" +
" \"sub\": \"4f229262-88d4-4a23-9fa5-2f5a0aadf16c\",\n" +
" \"typ\": \"Bearer\",\n" +
" \"azp\": \"account-console\",\n" +
" \"nonce\": \"50d8b172-15fd-4510-889e-c66c21e13176\",\n" +
" \"session_state\": \"7128671b-3f29-4971-8115-1ee743bbcd55\",\n" +
" \"acr\": \"0\",\n" +
getRealmAndResourceRolesClaims(includeRealmAndResourcesRoles) +
" \"scope\": \"openid email profile\",\n" +
" \"sid\": \"7128671b-3f29-4971-8115-1ee743bbcd55\",\n" +
" \"email_verified\": false,\n" +
getStandardRolesClaim(useEmptyRoles) +
" \"preferred_username\": \"alice\",\n" +
" \"given_name\": \"\",\n" +
" \"family_name\": \"\"\n" +
"}\n");
}

private static String getStandardRolesClaim(boolean useEmptyRoles) {
return useEmptyRoles ? " \"roles\": [\n" +
" ],\n" :
" \"roles\": [\n" +
" \"roleE\",\n" +
" \"roleF\"\n" +
" ],\n";
}

private static String getRealmAndResourceRolesClaims(boolean includeRealmAndResourceRoles) {
return includeRealmAndResourceRoles ? " \"realm_access\": {\n" +
" \"roles\": [\n" +
" \"roleC\",\n" +
" \"roleD\"\n" +
" ]\n" +
" },\n" +
" \"resource_access\": {\n" +
" \"SomeResource\": {\n" +
" \"roles\": [\n" +
" \"roleA\"\n" +
" ]\n" +
" },\n" +
" \"rolesClient\": {\n" +
" \"roles\": [\n" +
" \"roleB\",\n" +
" \"roleC\"\n" +
" ]\n" +
" }\n" +
" },\n" :
"";
}

private Attributes.Entry getRealmIdentityRoles(OidcClientConfiguration clientConfiguration, JwtClaims jwtClaims) throws RealmUnavailableException {
RefreshableOidcSecurityContext securityContext = new RefreshableOidcSecurityContext(clientConfiguration,
null, null, new AccessToken(jwtClaims), null, null, null);
OidcPrincipal principal = new OidcPrincipal("john", securityContext);

RealmIdentity identity = realm.getRealmIdentity(principal);
AuthorizationIdentity authorizationIdentity = identity.getAuthorizationIdentity();
return authorizationIdentity.getAttributes().get(KEY_ROLES);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -290,4 +290,4 @@ private InputStream getOidcConfigurationInputStreamWithTokenSignatureAlgorithm()
"}";
return new ByteArrayInputStream(oidcConfig.getBytes(StandardCharsets.UTF_8));
}
}
}
Loading

0 comments on commit 4d83de9

Please sign in to comment.