Skip to content

Commit

Permalink
Merge remote-tracking branch 'redhat/2.2.x' into 2.x
Browse files Browse the repository at this point in the history
  • Loading branch information
fjuma committed Apr 10, 2024
2 parents 76b40c1 + bfab979 commit 4483acf
Show file tree
Hide file tree
Showing 15 changed files with 930 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,9 @@ public interface ElytronMessages extends BasicLogger {
@LogMessage(level = WARN)
@Message(id = 1181, value = "Not sending new request to jwks url \"%s\". Last request time was %d.")
void avoidingFetchJwks(URL url, long timestamp);

@LogMessage(level = WARN)
@Message(id = 1182, value = "Allowed jku values haven't been configured for the JWT validator. Token validation will fail if the token contains a 'jku' header parameter.")
void allowedJkuValuesNotConfigured();
}

Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import static org.wildfly.security.auth.realm.token._private.ElytronMessages.log;

Expand All @@ -54,20 +55,22 @@ class JwkManager {
private final Map<URL, CacheEntry> keys = new LinkedHashMap<>();
private final SSLContext sslContext;
private final HostnameVerifier hostnameVerifier;
private final Set<String> allowedJkuValues;

private final long updateTimeout;
private final int minTimeBetweenRequests;

private final int connectionTimeout;
private final int readTimeout;

JwkManager(SSLContext sslContext, HostnameVerifier hostnameVerifier, long updateTimeout, int connectionTimeout, int readTimeout, int minTimeBetweenRequests) {
JwkManager(SSLContext sslContext, HostnameVerifier hostnameVerifier, long updateTimeout, int connectionTimeout, int readTimeout, int minTimeBetweenRequests, Set<String> allowedJkuValues) {
this.sslContext = sslContext;
this.hostnameVerifier = hostnameVerifier;
this.updateTimeout = updateTimeout;
this.connectionTimeout = connectionTimeout;
this.readTimeout = readTimeout;
this.minTimeBetweenRequests = minTimeBetweenRequests;
this.allowedJkuValues = allowedJkuValues;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public static Builder builder() {

private final Set<String> issuers;
private final Set<String> audiences;
private final Set<String> allowedJkuValues;
private final JwkManager jwkManager;
private final Map<String, PublicKey> namedKeys;

Expand All @@ -85,12 +86,14 @@ public static Builder builder() {
JwtValidator(Builder configuration) {
this.issuers = checkNotNullParam("issuers", configuration.issuers);
this.audiences = checkNotNullParam("audience", configuration.audience);
this.allowedJkuValues = checkNotNullParam("allowedJkuValues", configuration.allowedJkuValues);
this.defaultPublicKey = configuration.publicKey;
this.namedKeys = configuration.namedKeys;
if (configuration.sslContext != null) {
this.jwkManager = new JwkManager(configuration.sslContext,
configuration.hostnameVerifier != null ? configuration.hostnameVerifier : HttpsURLConnection.getDefaultHostnameVerifier(),
configuration.updateTimeout, configuration.connectionTimeout, configuration.readTimeout, configuration.minTimeBetweenRequests);
configuration.updateTimeout, configuration.connectionTimeout, configuration.readTimeout, configuration.minTimeBetweenRequests,
configuration.allowedJkuValues);
}
else {
log.tokenRealmJwtNoSSLIgnoringJku();
Expand All @@ -106,6 +109,9 @@ public static Builder builder() {
if (audiences.isEmpty()) {
log.tokenRealmJwtWarnNoAudienceIgnoringAudienceCheck();
}
if (allowedJkuValues.isEmpty()) {
log.allowedJkuValuesNotConfigured();
}

}

Expand Down Expand Up @@ -311,6 +317,10 @@ private PublicKey resolvePublicKey(JsonObject headers) {
log.debugf("Cannot validate token with jku [%s]. SSL is not configured and jku claim is not supported.", jku);
return null;
}
if (! allowedJkuValues.contains(jku.getString())) {
log.debug("Cannot validate token, jku value is not allowed");
return null;
}
try {
return jwkManager.getPublicKey(kid.getString(), new URL(jku.getString()));
} catch (MalformedURLException e) {
Expand Down Expand Up @@ -340,6 +350,7 @@ public static class Builder {

private Set<String> issuers = new LinkedHashSet<>();
private Set<String> audience = new LinkedHashSet<>();
private Set<String> allowedJkuValues = new LinkedHashSet<>();
private PublicKey publicKey;
private Map<String, PublicKey> namedKeys = new LinkedHashMap<>();
private HostnameVerifier hostnameVerifier;
Expand Down Expand Up @@ -495,6 +506,19 @@ public Builder setJkuMinTimeBetweenRequests(int minTimeBetweenRequests) {
return this;
}

/**
* One or more string values representing the jku values that are supported by this configuration.
* During JWT validation, if the jku header parameter is present in a token, it must exactly match
* one of the strings defined here or token validation will fail.
*
* @param allowedJkuValues the allowed values for the jku header parameter
* @return this instance
*/
public Builder setAllowedJkuValues(String... allowedJkuValues) {
this.allowedJkuValues.addAll(asList(allowedJkuValues));
return this;
}

/**
* Returns a {@link JwtValidator} instance based on all the configuration provided with this builder.
*
Expand Down
14 changes: 14 additions & 0 deletions http/oidc/src/main/java/org/wildfly/security/http/oidc/Oidc.java
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,18 @@ public static void logToken(String name, String token) {
}
}

protected static boolean checkCachedAccountMatchesRequest(OidcAccount account, OidcClientConfiguration deployment) {
if (deployment.getRealm() != null
&& ! deployment.getRealm().equals(account.getOidcSecurityContext().getRealm())) {
log.debug("Account in session belongs to a different realm than for this request.");
return false;
}
if (deployment.getProviderUrl() != null
&& ! deployment.getProviderUrl().equals(account.getOidcSecurityContext().getOidcClientConfiguration().getProviderUrl())) {
log.debug("Account in session belongs to a different provider than for this request.");
return false;
}
return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import static org.wildfly.security.http.oidc.ElytronMessages.log;
import static org.wildfly.security.http.oidc.Oidc.OIDC_STATE_COOKIE;
import static org.wildfly.security.http.oidc.Oidc.checkCachedAccountMatchesRequest;

import java.net.URISyntaxException;
import java.util.List;
Expand Down Expand Up @@ -72,8 +73,7 @@ public boolean isCached(RequestAuthenticator authenticator) {
return false;
}
OidcAccount account = new OidcAccount(principal);
if (deployment.getRealm() != null && ! deployment.getRealm().equals(account.getOidcSecurityContext().getRealm())) {
log.debug("Account in session belongs to a different realm than for this request.");
if (! checkCachedAccountMatchesRequest(account, deployment)) {
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.wildfly.security.http.oidc;

import static org.wildfly.security.http.oidc.ElytronMessages.log;
import static org.wildfly.security.http.oidc.Oidc.checkCachedAccountMatchesRequest;

import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -88,9 +89,7 @@ public boolean isCached(RequestAuthenticator authenticator) {
}

OidcClientConfiguration deployment = httpFacade.getOidcClientConfiguration();

if (deployment.getRealm() != null && ! deployment.getRealm().equals(account.getOidcSecurityContext().getRealm())) {
log.debug("Account in session belongs to a different realm than for this request.");
if (! checkCachedAccountMatchesRequest(account, deployment)) {
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@

package org.wildfly.security.http.oidc;

import static org.wildfly.security.http.oidc.OidcBaseTest.TENANT1_REALM;
import static org.wildfly.security.http.oidc.OidcBaseTest.TENANT2_REALM;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand Down Expand Up @@ -51,6 +54,16 @@ public class KeycloakConfiguration {
public static final String ALLOWED_ORIGIN = "http://somehost";
public static final boolean EMAIL_VERIFIED = false;

// the users below are for multi-tenancy tests specifically
public static final String TENANT1_USER = "tenant1_user";
public static final String TENANT1_PASSWORD = "tenant1_password";
public static final String TENANT2_USER = "tenant2_user";
public static final String TENANT2_PASSWORD = "tenant2_password";
public static final String CHARLIE = "charlie";
public static final String CHARLIE_PASSWORD =" charlie123+";
public static final String DAN = "dan";
public static final String DAN_PASSWORD =" dan123+";

/**
* Configure RealmRepresentation as follows:
* <ul>
Expand All @@ -67,6 +80,12 @@ public static RealmRepresentation getRealmRepresentation(final String realmName,
return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp, configureClientScopes);
}

public static RealmRepresentation getRealmRepresentation(final String realmName, String clientId, String clientSecret,
String clientHostName, int clientPort, String clientApp, int accessTokenLifespan,
int ssoSessionMaxLifespan, boolean configureClientScopes, boolean multiTenancyApp) {
return createRealm(realmName, clientId, clientSecret, clientHostName, clientPort, clientApp, accessTokenLifespan, ssoSessionMaxLifespan, configureClientScopes, multiTenancyApp);
}

public static RealmRepresentation getRealmRepresentation(final String realmName, String clientId, String clientSecret,
String clientHostName, int clientPort, String clientApp,
boolean directAccessGrantEnabled, String bearerOnlyClientId,
Expand Down Expand Up @@ -116,18 +135,31 @@ private static RealmRepresentation createRealm(String name, String clientId, Str
return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, false, null, null, configureClientScopes);
}

private static RealmRepresentation createRealm(String name, String clientId, String clientSecret,
String clientHostName, int clientPort, String clientApp, int accessTokenLifeSpan, int ssoSessionMaxLifespan,
boolean configureClientScopes, boolean multiTenancyApp) {
return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, false, null, null, accessTokenLifeSpan, ssoSessionMaxLifespan, configureClientScopes, multiTenancyApp);
}

private static RealmRepresentation createRealm(String name, String clientId, String clientSecret,
String clientHostName, int clientPort, String clientApp,
boolean directAccessGrantEnabled, String bearerOnlyClientId,
String corsClientId, boolean configureClientScopes) {
RealmRepresentation realm = new RealmRepresentation();
return createRealm(name, clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, bearerOnlyClientId, corsClientId, 3, 3, configureClientScopes, false);
}

private static RealmRepresentation createRealm(String name, String clientId, String clientSecret,
String clientHostName, int clientPort, String clientApp,
boolean directAccessGrantEnabled, String bearerOnlyClientId,
String corsClientId, int accessTokenLifespan, int ssoSessionMaxLifespan,
boolean configureClientScopes, boolean multiTenancyApp) {
RealmRepresentation realm = new RealmRepresentation();
realm.setRealm(name);
realm.setEnabled(true);
realm.setUsers(new ArrayList<>());
realm.setClients(new ArrayList<>());
realm.setAccessTokenLifespan(3);
realm.setSsoSessionMaxLifespan(3);
realm.setAccessTokenLifespan(accessTokenLifespan);
realm.setSsoSessionMaxLifespan(ssoSessionMaxLifespan);

RolesRepresentation roles = new RolesRepresentation();
List<RoleRepresentation> realmRoles = new ArrayList<>();
Expand All @@ -137,7 +169,8 @@ private static RealmRepresentation createRealm(String name, String clientId, Str

realm.getRoles().getRealm().add(new RoleRepresentation("user", null, false));
realm.getRoles().getRealm().add(new RoleRepresentation("admin", null, false));
ClientRepresentation webAppClient = createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled);

ClientRepresentation webAppClient = createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, multiTenancyApp);
if (configureClientScopes) {
webAppClient.setDefaultClientScopes(Collections.singletonList(OIDC_SCOPE));
webAppClient.setOptionalClientScopes(Arrays.asList("phone", "email", "profile"));
Expand All @@ -149,26 +182,46 @@ private static RealmRepresentation createRealm(String name, String clientId, Str
}

if (corsClientId != null) {
realm.getClients().add(createWebAppClient(corsClientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, ALLOWED_ORIGIN));
realm.getClients().add(createWebAppClient(corsClientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, ALLOWED_ORIGIN, multiTenancyApp));
}

realm.getUsers().add(createUser(ALICE, ALICE_PASSWORD, Arrays.asList(USER_ROLE, ADMIN_ROLE)));
realm.getUsers().add(createUser(BOB, BOB_PASSWORD, Arrays.asList(USER_ROLE)));
if (name.equals(TENANT1_REALM)) {
realm.getUsers().add(createUser(TENANT1_USER, TENANT1_PASSWORD, Arrays.asList(USER_ROLE, ADMIN_ROLE)));
realm.getUsers().add(createUser(CHARLIE, CHARLIE_PASSWORD, Arrays.asList(USER_ROLE, ADMIN_ROLE)));
realm.getUsers().add(createUser(DAN, DAN_PASSWORD, Arrays.asList(USER_ROLE, ADMIN_ROLE)));
} else if (name.equals(TENANT2_REALM)) {
realm.getUsers().add(createUser(TENANT2_USER, TENANT2_PASSWORD, Arrays.asList(USER_ROLE, ADMIN_ROLE)));
realm.getUsers().add(createUser(CHARLIE, CHARLIE_PASSWORD, Arrays.asList(USER_ROLE, ADMIN_ROLE)));
realm.getUsers().add(createUser(DAN, DAN_PASSWORD, Arrays.asList(USER_ROLE, ADMIN_ROLE)));
} else {
realm.getUsers().add(createUser(ALICE, ALICE_PASSWORD, Arrays.asList(USER_ROLE, ADMIN_ROLE)));
realm.getUsers().add(createUser(BOB, BOB_PASSWORD, Arrays.asList(USER_ROLE)));
}
return realm;
}

private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort, String clientApp, boolean directAccessGrantEnabled) {
return createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, null);
private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort, String clientApp,
boolean directAccessGrantEnabled, boolean multiTenancyApp) {
return createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, null, multiTenancyApp);
}

private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort,
String clientApp, boolean directAccessGrantEnabled, String allowedOrigin) {
return createWebAppClient(clientId, clientSecret, clientHostName, clientPort, clientApp, directAccessGrantEnabled, allowedOrigin, false);
}

private static ClientRepresentation createWebAppClient(String clientId, String clientSecret, String clientHostName, int clientPort,
String clientApp, boolean directAccessGrantEnabled, String allowedOrigin, boolean multiTenancyApp) {
ClientRepresentation client = new ClientRepresentation();
client.setClientId(clientId);
client.setPublicClient(false);
client.setSecret(clientSecret);
//client.setRedirectUris(Arrays.asList("*"));
client.setRedirectUris(Arrays.asList("http://" + clientHostName + ":" + clientPort + "/" + clientApp));
if (multiTenancyApp) {
client.setRedirectUris(Arrays.asList("http://" + clientHostName + ":" + clientPort + "/" + clientApp + "/*"));
} else {
client.setRedirectUris(Arrays.asList("http://" + clientHostName + ":" + clientPort + "/" + clientApp));
}
client.setEnabled(true);
client.setDirectAccessGrantsEnabled(directAccessGrantEnabled);
if (allowedOrigin != null) {
Expand Down
Loading

0 comments on commit 4483acf

Please sign in to comment.