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

HTTP Authorization Refactor #4979

Closed
Closed
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 @@ -6,6 +6,7 @@
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;

Expand All @@ -18,45 +19,45 @@
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.adapters.config.PolicyEnforcerConfig;

import io.quarkus.arc.AlternativePriority;
import io.quarkus.oidc.runtime.OidcConfig;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.security.runtime.QuarkusSecurityIdentity;
import io.quarkus.vertx.http.runtime.security.HttpAuthorizer;
import io.quarkus.vertx.http.runtime.security.QuarkusHttpUser;
import io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy;
import io.vertx.ext.web.RoutingContext;

@Singleton
@AlternativePriority(1)
public class KeycloakPolicyEnforcerAuthorizer extends HttpAuthorizer {
public class KeycloakPolicyEnforcerAuthorizer
implements HttpSecurityPolicy, BiFunction<RoutingContext, SecurityIdentity, HttpSecurityPolicy.CheckResult> {

private KeycloakAdapterPolicyEnforcer delegate;

@Override
public CompletionStage<SecurityIdentity> checkPermission(RoutingContext routingContext) {
public CompletionStage<CheckResult> checkPermission(RoutingContext request, SecurityIdentity identity,
AuthorizationRequestContext requestContext) {
return requestContext.runBlocking(request, identity, this);
}

@Override
public CheckResult apply(RoutingContext routingContext, SecurityIdentity identity) {

VertxHttpFacade httpFacade = new VertxHttpFacade(routingContext);
AuthorizationContext result = delegate.authorize(httpFacade);

if (result.isGranted()) {
QuarkusHttpUser user = (QuarkusHttpUser) routingContext.user();

if (user == null) {
return attemptAnonymousAuthentication(routingContext);
}

return enhanceSecurityIdentity(user.getSecurityIdentity(), result);
SecurityIdentity newIdentity = enhanceSecurityIdentity(identity, result);
return new CheckResult(true, newIdentity);
}

return CompletableFuture.completedFuture(null);
return CheckResult.DENY;
}

private CompletableFuture<SecurityIdentity> enhanceSecurityIdentity(SecurityIdentity current,
private SecurityIdentity enhanceSecurityIdentity(SecurityIdentity current,
AuthorizationContext context) {
Map<String, Object> attributes = new HashMap<>(current.getAttributes());

attributes.put("permissions", context.getPermissions());

return CompletableFuture.completedFuture(new QuarkusSecurityIdentity.Builder()
return new QuarkusSecurityIdentity.Builder()
.addAttributes(attributes)
.setPrincipal(current.getPrincipal())
.addRoles(current.getRoles())
Expand All @@ -82,7 +83,7 @@ public CompletionStage<Boolean> apply(Permission permission) {

return CompletableFuture.completedFuture(false);
}
}).build());
}).build();
}

public void init(OidcConfig oidcConfig, KeycloakPolicyEnforcerConfig config) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import org.jboss.logging.Logger;

import io.quarkus.runtime.BlockingOperationControl;
import io.quarkus.security.AuthenticationFailedException;
import io.quarkus.security.identity.AuthenticationRequestContext;
import io.quarkus.security.identity.IdentityProvider;
Expand All @@ -32,17 +33,33 @@ public class QuarkusIdentityProviderManagerImpl implements IdentityProviderManag
private final List<SecurityIdentityAugmentor> augmenters;
private final Executor blockingExecutor;

private static final AuthenticationRequestContext blockingRequestContext = new AuthenticationRequestContext() {
private final AuthenticationRequestContext blockingRequestContext = new AuthenticationRequestContext() {
@Override
public CompletionStage<SecurityIdentity> runBlocking(Supplier<SecurityIdentity> function) {
CompletableFuture<SecurityIdentity> ret = new CompletableFuture<>();
try {
SecurityIdentity result = function.get();
ret.complete(result);
} catch (Throwable t) {
ret.completeExceptionally(t);

if (BlockingOperationControl.isBlockingAllowed()) {
CompletableFuture<SecurityIdentity> ret = new CompletableFuture<>();
try {
SecurityIdentity result = function.get();
ret.complete(result);
} catch (Throwable t) {
ret.completeExceptionally(t);
}
return ret;
} else {
CompletableFuture<SecurityIdentity> cf = new CompletableFuture<>();
blockingExecutor.execute(new Runnable() {
@Override
public void run() {
try {
cf.complete(function.get());
} catch (Throwable t) {
cf.completeExceptionally(t);
}
}
});
return cf;
}
return ret;
}
};

Expand All @@ -69,7 +86,7 @@ public CompletionStage<SecurityIdentity> authenticate(AuthenticationRequest requ
"No IdentityProviders were registered to handle AuthenticationRequest " + request));
return cf;
}
return handleProvider(0, (List) providers, request, new AsyncAuthenticationRequestContext());
return handleProvider(0, (List) providers, request, blockingRequestContext);
}

/**
Expand Down Expand Up @@ -211,31 +228,4 @@ public int compare(SecurityIdentityAugmentor o1, SecurityIdentityAugmentor o2) {
}
}

private class AsyncAuthenticationRequestContext implements AuthenticationRequestContext {

private boolean inBlocking = false;

@Override
public CompletionStage<SecurityIdentity> runBlocking(Supplier<SecurityIdentity> function) {
if (inBlocking) {
return blockingRequestContext.runBlocking(function);
}
CompletableFuture<SecurityIdentity> cf = new CompletableFuture<>();
blockingExecutor.execute(new Runnable() {
@Override
public void run() {
try {
inBlocking = true;
cf.complete(function.get());
} catch (Throwable t) {
cf.completeExceptionally(t);
} finally {
inBlocking = false;
}
}
});

return cf;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,28 @@
import io.quarkus.vertx.http.runtime.security.HttpAuthorizer;
import io.quarkus.vertx.http.runtime.security.HttpSecurityPolicy;
import io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder;
import io.quarkus.vertx.http.runtime.security.PathMatchingHttpSecurityPolicy;
import io.quarkus.vertx.http.runtime.security.PermitSecurityPolicy;
import io.quarkus.vertx.http.runtime.security.RolesAllowedHttpSecurityPolicy;
import io.quarkus.vertx.http.runtime.security.SupplierImpl;

public class HttpSecurityProcessor {

@BuildStep
public void builtins(BuildProducer<HttpSecurityPolicyBuildItem> producer, HttpBuildTimeConfig buildTimeConfig) {
public void builtins(BuildProducer<HttpSecurityPolicyBuildItem> producer, HttpBuildTimeConfig buildTimeConfig,
BuildProducer<AdditionalBeanBuildItem> beanProducer) {
producer.produce(new HttpSecurityPolicyBuildItem("deny", new SupplierImpl<>(new DenySecurityPolicy())));
producer.produce(new HttpSecurityPolicyBuildItem("permit", new SupplierImpl<>(new PermitSecurityPolicy())));
producer.produce(
new HttpSecurityPolicyBuildItem("authenticated", new SupplierImpl<>(new AuthenticatedHttpSecurityPolicy())));

if (!buildTimeConfig.auth.permissions.isEmpty()) {
beanProducer.produce(AdditionalBeanBuildItem.unremovableOf(PathMatchingHttpSecurityPolicy.class));
}
for (Map.Entry<String, PolicyConfig> e : buildTimeConfig.auth.rolePolicy.entrySet()) {
producer.produce(new HttpSecurityPolicyBuildItem(e.getKey(),
new SupplierImpl<>(new RolesAllowedHttpSecurityPolicy(e.getValue().rolesAllowed))));
}

}

@BuildStep
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
import java.util.concurrent.CompletionStage;

import io.quarkus.security.identity.SecurityIdentity;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.ext.web.RoutingContext;

/**
* permission checker that checks if the user is authenticated
*/
public class AuthenticatedHttpSecurityPolicy implements HttpSecurityPolicy {

@Override
public CompletionStage<CheckResult> checkPermission(HttpServerRequest request, SecurityIdentity identity) {
public CompletionStage<CheckResult> checkPermission(RoutingContext request, SecurityIdentity identity,
AuthorizationRequestContext requestContext) {
return CompletableFuture.completedFuture(identity.isAnonymous() ? CheckResult.DENY : CheckResult.PERMIT);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
import java.util.concurrent.CompletionStage;

import io.quarkus.security.identity.SecurityIdentity;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.ext.web.RoutingContext;

public class DenySecurityPolicy implements HttpSecurityPolicy {

public static final DenySecurityPolicy INSTANCE = new DenySecurityPolicy();

@Override
public CompletionStage<CheckResult> checkPermission(HttpServerRequest request, SecurityIdentity identity) {
public CompletionStage<CheckResult> checkPermission(RoutingContext request, SecurityIdentity identity,
AuthorizationRequestContext requestContext) {
return CompletableFuture.completedFuture(CheckResult.DENY);
}
}
Loading