Skip to content

Commit

Permalink
Support SpEL Returning AuthorizationDecision
Browse files Browse the repository at this point in the history
  • Loading branch information
jzheaux committed Apr 4, 2024
1 parent 9b79f68 commit 9dba87c
Show file tree
Hide file tree
Showing 20 changed files with 382 additions and 196 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,30 @@
import java.util.function.Supplier;

import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ObservationAuthorizationManager;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
import org.springframework.security.core.Authentication;
import org.springframework.util.function.SingletonSupplier;

final class DeferringObservationAuthorizationManager<T> implements AuthorizationManager<T> {
final class DeferringObservationAuthorizationManager<T>
implements AuthorizationManager<T>, MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {

private final Supplier<AuthorizationManager<T>> delegate;

private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();

private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();

DeferringObservationAuthorizationManager(ObjectProvider<ObservationRegistry> provider,
AuthorizationManager<T> delegate) {
this.delegate = SingletonSupplier.of(() -> {
Expand All @@ -40,11 +52,28 @@ final class DeferringObservationAuthorizationManager<T> implements Authorization
}
return new ObservationAuthorizationManager<>(registry, delegate);
});
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
this.handler = h;
}
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
this.postProcessor = p;
}
}

@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, T object) {
return this.delegate.get().check(authentication, object);
}

@Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return this.handler.handle(methodInvocation, authorizationResult);
}

@Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,31 @@
import java.util.function.Supplier;

import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;
import reactor.core.publisher.Mono;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.ObservationReactiveAuthorizationManager;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
import org.springframework.security.core.Authentication;
import org.springframework.util.function.SingletonSupplier;

final class DeferringObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T> {
final class DeferringObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T>,
MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {

private final Supplier<ReactiveAuthorizationManager<T>> delegate;

private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();

private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();

DeferringObservationReactiveAuthorizationManager(ObjectProvider<ObservationRegistry> provider,
ReactiveAuthorizationManager<T> delegate) {
this.delegate = SingletonSupplier.of(() -> {
Expand All @@ -41,11 +53,28 @@ final class DeferringObservationReactiveAuthorizationManager<T> implements React
}
return new ObservationReactiveAuthorizationManager<>(registry, delegate);
});
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
this.handler = h;
}
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
this.postProcessor = p;
}
}

@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> authentication, T object) {
return this.delegate.get().check(authentication, object);
}

@Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return this.handler.handle(methodInvocation, authorizationResult);
}

@Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,12 +19,42 @@
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Expression;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ExpressionAuthorizationDecision;

public final class ExpressionUtils {

private ExpressionUtils() {
}

/**
* Evaluate a SpEL expression and coerce into an {@link AuthorizationDecision}
* @param expr a SpEL expression
* @param ctx an {@link EvaluationContext}
* @return the resulting {@link AuthorizationDecision}
* @since 6.3
*/
public static AuthorizationDecision evaluate(Expression expr, EvaluationContext ctx) {
try {
Object result = expr.getValue(ctx);
if (result instanceof AuthorizationDecision decision) {
return decision;
}
if (result instanceof Boolean granted) {
return new ExpressionAuthorizationDecision(granted, expr);
}
if (result == null) {
return null;
}
throw new IllegalArgumentException(
"SpEL expression must return either a Boolean or an AuthorizationDecision");
}
catch (EvaluationException ex) {
throw new IllegalArgumentException("Failed to evaluate expression '" + expr.getExpressionString() + "'",
ex);
}
}

public static boolean evaluateAsBoolean(Expression expr, EvaluationContext ctx) {
try {
return expr.getValue(ctx, Boolean.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,17 @@
import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationConvention;
import io.micrometer.observation.ObservationRegistry;
import org.aopalliance.intercept.MethodInvocation;

import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.util.Assert;
Expand All @@ -36,7 +42,8 @@
* @author Josh Cummings
* @since 6.0
*/
public final class ObservationAuthorizationManager<T> implements AuthorizationManager<T>, MessageSourceAware {
public final class ObservationAuthorizationManager<T> implements AuthorizationManager<T>, MessageSourceAware,
MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {

private final ObservationRegistry registry;

Expand All @@ -46,9 +53,19 @@ public final class ObservationAuthorizationManager<T> implements AuthorizationMa

private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();

private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();

public ObservationAuthorizationManager(ObservationRegistry registry, AuthorizationManager<T> delegate) {
this.registry = registry;
this.delegate = delegate;
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
this.handler = h;
}
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
this.postProcessor = p;
}
}

@Override
Expand Down Expand Up @@ -98,4 +115,15 @@ public void setMessageSource(final MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}

@Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return this.handler.handle(methodInvocation, authorizationResult);
}

@Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@
import io.micrometer.observation.ObservationConvention;
import io.micrometer.observation.ObservationRegistry;
import io.micrometer.observation.contextpropagation.ObservationThreadLocalAccessor;
import org.aopalliance.intercept.MethodInvocation;
import reactor.core.publisher.Mono;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor;
import org.springframework.security.authorization.method.MethodInvocationResult;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedHandler;
import org.springframework.security.authorization.method.ThrowingMethodAuthorizationDeniedPostProcessor;
import org.springframework.security.core.Authentication;
import org.springframework.util.Assert;

Expand All @@ -32,18 +38,29 @@
* @author Josh Cummings
* @since 6.0
*/
public final class ObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T> {
public final class ObservationReactiveAuthorizationManager<T> implements ReactiveAuthorizationManager<T>,
MethodAuthorizationDeniedHandler, MethodAuthorizationDeniedPostProcessor {

private final ObservationRegistry registry;

private final ReactiveAuthorizationManager<T> delegate;

private ObservationConvention<AuthorizationObservationContext<?>> convention = new AuthorizationObservationConvention();

private MethodAuthorizationDeniedHandler handler = new ThrowingMethodAuthorizationDeniedHandler();

private MethodAuthorizationDeniedPostProcessor postProcessor = new ThrowingMethodAuthorizationDeniedPostProcessor();

public ObservationReactiveAuthorizationManager(ObservationRegistry registry,
ReactiveAuthorizationManager<T> delegate) {
this.registry = registry;
this.delegate = delegate;
if (delegate instanceof MethodAuthorizationDeniedHandler h) {
this.handler = h;
}
if (delegate instanceof MethodAuthorizationDeniedPostProcessor p) {
this.postProcessor = p;
}
}

@Override
Expand Down Expand Up @@ -81,4 +98,15 @@ public void setObservationConvention(ObservationConvention<AuthorizationObservat
this.convention = convention;
}

@Override
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
return this.handler.handle(methodInvocation, authorizationResult);
}

@Override
public Object postProcessResult(MethodInvocationResult methodInvocationResult,
AuthorizationResult authorizationResult) {
return this.postProcessor.postProcessResult(methodInvocationResult, authorizationResult);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ private Object attemptAuthorization(MethodInvocation mi, Object result) {
}

private Object postProcess(MethodInvocationResult mi, AuthorizationDecision decision) {
if (decision instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
return postProcessableDecision.postProcessResult(mi, decision);
}
return this.defaultPostProcessor.postProcessResult(mi, decision);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ private Mono<Object> postProcess(AuthorizationDecision decision, MethodInvocatio
return Mono.just(methodInvocationResult.getResult());
}
return Mono.fromSupplier(() -> {
if (decision instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedPostProcessor postProcessableDecision) {
return postProcessableDecision.postProcessResult(methodInvocationResult, decision);
}
return this.defaultPostProcessor.postProcessResult(methodInvocationResult, decision);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ private Object attemptAuthorization(MethodInvocation mi) throws Throwable {
}

private Object handle(MethodInvocation mi, AuthorizationDecision decision) {
if (decision instanceof MethodAuthorizationDeniedHandler handler) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
return handler.handle(mi, decision);
}
return this.defaultHandler.handle(mi, decision);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ private Mono<Object> preAuthorized(MethodInvocation mi, Mono<Object> mapping) {

private Mono<Object> postProcess(AuthorizationDecision decision, MethodInvocation mi) {
return Mono.fromSupplier(() -> {
if (decision instanceof MethodAuthorizationDeniedHandler handler) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler handler) {
return handler.handle(mi, decision);
}
return this.defaultHandler.handle(mi, decision);
Expand Down

This file was deleted.

Loading

0 comments on commit 9dba87c

Please sign in to comment.