-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Keycloak Authorization dynamic tenant config resolution
- Loading branch information
1 parent
814958b
commit 56072ca
Showing
21 changed files
with
1,332 additions
and
355 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
...k-authorization/runtime/src/main/java/io/quarkus/keycloak/pep/PolicyEnforcerResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package io.quarkus.keycloak.pep; | ||
|
||
import org.keycloak.adapters.authorization.PolicyEnforcer; | ||
|
||
import io.smallrye.mutiny.Uni; | ||
import io.vertx.ext.web.RoutingContext; | ||
|
||
/** | ||
* A {@link PolicyEnforcer} resolver. | ||
*/ | ||
public interface PolicyEnforcerResolver { | ||
|
||
Uni<PolicyEnforcer> resolvePolicyEnforcer(RoutingContext routingContext, String tenantId); | ||
|
||
long getReadTimeout(); | ||
|
||
} |
34 changes: 34 additions & 0 deletions
34
...thorization/runtime/src/main/java/io/quarkus/keycloak/pep/TenantPolicyConfigResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package io.quarkus.keycloak.pep; | ||
|
||
import java.util.function.Supplier; | ||
|
||
import io.quarkus.keycloak.pep.runtime.KeycloakPolicyEnforcerTenantConfig; | ||
import io.smallrye.mutiny.Uni; | ||
import io.vertx.ext.web.RoutingContext; | ||
|
||
/** | ||
* A tenant resolver is responsible for resolving the {@link KeycloakPolicyEnforcerTenantConfig} for tenants, dynamically. | ||
*/ | ||
public interface TenantPolicyConfigResolver { | ||
|
||
/** | ||
* Returns a {@link KeycloakPolicyEnforcerTenantConfig} given a {@code RoutingContext} and tenant id. | ||
* | ||
* @param routingContext the routing context | ||
* @param tenantId tenant id | ||
* @param requestContext request context | ||
* | ||
* @return the tenant configuration. If the uni resolves to {@code null}, indicates that the default | ||
* configuration/tenant should be chosen | ||
*/ | ||
Uni<KeycloakPolicyEnforcerTenantConfig> resolve(RoutingContext routingContext, String tenantId, | ||
KeycloakRequestContext requestContext); | ||
|
||
/** | ||
* Keycloak Context that can be used to run blocking tasks. | ||
*/ | ||
interface KeycloakRequestContext { | ||
<T> Uni<T> runBlocking(Supplier<T> function); | ||
} | ||
|
||
} |
118 changes: 118 additions & 0 deletions
118
.../runtime/src/main/java/io/quarkus/keycloak/pep/runtime/DefaultPolicyEnforcerResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package io.quarkus.keycloak.pep.runtime; | ||
|
||
import static io.quarkus.keycloak.pep.runtime.KeycloakPolicyEnforcerUtil.createPolicyEnforcer; | ||
import static io.quarkus.keycloak.pep.runtime.KeycloakPolicyEnforcerUtil.getOidcTenantConfig; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.function.Function; | ||
import java.util.function.Supplier; | ||
|
||
import jakarta.enterprise.inject.Instance; | ||
import jakarta.inject.Singleton; | ||
|
||
import org.keycloak.adapters.authorization.PolicyEnforcer; | ||
|
||
import io.quarkus.keycloak.pep.PolicyEnforcerResolver; | ||
import io.quarkus.keycloak.pep.TenantPolicyConfigResolver; | ||
import io.quarkus.oidc.OidcTenantConfig; | ||
import io.quarkus.oidc.runtime.OidcConfig; | ||
import io.quarkus.runtime.TlsConfig; | ||
import io.quarkus.security.spi.runtime.BlockingSecurityExecutor; | ||
import io.quarkus.vertx.http.runtime.HttpConfiguration; | ||
import io.smallrye.mutiny.Uni; | ||
import io.vertx.ext.web.RoutingContext; | ||
|
||
@Singleton | ||
public class DefaultPolicyEnforcerResolver implements PolicyEnforcerResolver { | ||
|
||
private final TenantPolicyConfigResolver dynamicConfigResolver; | ||
private final TenantPolicyConfigResolver.KeycloakRequestContext requestContext; | ||
private final Map<String, PolicyEnforcer> namedPolicyEnforcers; | ||
private final PolicyEnforcer defaultPolicyEnforcer; | ||
private final long readTimeout; | ||
private final boolean tlsConfigTrustAll; | ||
private final OidcConfig oidcConfig; | ||
|
||
DefaultPolicyEnforcerResolver(OidcConfig oidcConfig, KeycloakPolicyEnforcerConfig config, TlsConfig tlsConfig, | ||
HttpConfiguration httpConfiguration, BlockingSecurityExecutor blockingSecurityExecutor, | ||
Instance<TenantPolicyConfigResolver> configResolver) { | ||
this.readTimeout = httpConfiguration.readTimeout.toMillis(); | ||
this.oidcConfig = oidcConfig; | ||
this.tlsConfigTrustAll = tlsConfig.trustAll; | ||
this.defaultPolicyEnforcer = createPolicyEnforcer(oidcConfig.defaultTenant, config.defaultTenant(), tlsConfigTrustAll); | ||
this.namedPolicyEnforcers = createNamedPolicyEnforcers(oidcConfig, config, tlsConfigTrustAll); | ||
if (configResolver.isResolvable()) { | ||
this.dynamicConfigResolver = configResolver.get(); | ||
this.requestContext = createKeycloakRequestContext(blockingSecurityExecutor); | ||
} else { | ||
this.dynamicConfigResolver = null; | ||
this.requestContext = null; | ||
} | ||
} | ||
|
||
@Override | ||
public Uni<PolicyEnforcer> resolvePolicyEnforcer(RoutingContext routingContext, String tenantId) { | ||
if (dynamicConfigResolver == null) { | ||
return Uni.createFrom().item(getStaticPolicyEnforcer(tenantId)); | ||
} else { | ||
return getDynamicPolicyEnforcer(routingContext, tenantId) | ||
.onItem().ifNull().continueWith(new Supplier<PolicyEnforcer>() { | ||
@Override | ||
public PolicyEnforcer get() { | ||
return getStaticPolicyEnforcer(tenantId); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
@Override | ||
public long getReadTimeout() { | ||
return readTimeout; | ||
} | ||
|
||
PolicyEnforcer getStaticPolicyEnforcer(String tenantId) { | ||
return tenantId != null && namedPolicyEnforcers.containsKey(tenantId) | ||
? namedPolicyEnforcers.get(tenantId) | ||
: defaultPolicyEnforcer; | ||
} | ||
|
||
boolean hasDynamicPolicyEnforcers() { | ||
return dynamicConfigResolver != null; | ||
} | ||
|
||
private Uni<PolicyEnforcer> getDynamicPolicyEnforcer(RoutingContext routingContext, String tenantId) { | ||
return dynamicConfigResolver.resolve(routingContext, tenantId, requestContext) | ||
.onItem().ifNotNull().transform(new Function<KeycloakPolicyEnforcerTenantConfig, PolicyEnforcer>() { | ||
@Override | ||
public PolicyEnforcer apply(KeycloakPolicyEnforcerTenantConfig tenant) { | ||
return createPolicyEnforcer(tenant, tlsConfigTrustAll, tenantId, oidcConfig); | ||
} | ||
}); | ||
} | ||
|
||
private static Map<String, PolicyEnforcer> createNamedPolicyEnforcers(OidcConfig oidcConfig, | ||
KeycloakPolicyEnforcerConfig config, boolean tlsConfigTrustAll) { | ||
if (config.namedTenants().isEmpty()) { | ||
return Map.of(); | ||
} | ||
|
||
Map<String, PolicyEnforcer> policyEnforcerTenants = new HashMap<>(); | ||
for (Map.Entry<String, KeycloakPolicyEnforcerTenantConfig> tenant : config.namedTenants().entrySet()) { | ||
OidcTenantConfig oidcTenantConfig = getOidcTenantConfig(oidcConfig, tenant.getKey()); | ||
policyEnforcerTenants.put(tenant.getKey(), | ||
createPolicyEnforcer(oidcTenantConfig, tenant.getValue(), tlsConfigTrustAll)); | ||
} | ||
return Map.copyOf(policyEnforcerTenants); | ||
} | ||
|
||
private static TenantPolicyConfigResolver.KeycloakRequestContext createKeycloakRequestContext( | ||
BlockingSecurityExecutor blockingSecurityExecutor) { | ||
return new TenantPolicyConfigResolver.KeycloakRequestContext() { | ||
@Override | ||
public <T> Uni<T> runBlocking(Supplier<T> function) { | ||
return blockingSecurityExecutor.executeBlocking(function); | ||
} | ||
}; | ||
} | ||
} |
Oops, something went wrong.