Add AfterMethodAuthorizationManager

- Removes the need to keep MethodAuthorizationContext#returnObject
in sync with other method parameters
- Restores MethodAuthorizationContext's immutability

Closes gh-9591
This commit is contained in:
Josh Cummings
2021-04-08 11:36:03 -06:00
parent 2b494ebc5f
commit 6828987b4b
12 changed files with 176 additions and 74 deletions

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2002-2021 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import java.util.function.Supplier;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.lang.Nullable;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
/**
* An Authorization manager which can determine if an {@link Authentication} has access to
* a specific object and associated return object. Intended for use specifically to
* evaluate the returning state of a method invocation.
*
* @param <T> the type of object that the authorization check is being done one.
* @author Josh Cummings
* @author Evgeniy Cheban
* @since 5.5
*/
public interface AfterMethodAuthorizationManager<T> {
/**
* Determines if access should be granted for a specific authentication and
* returnedObject.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param object the {@code T} object to check, typically a {@link MethodInvocation}
* @param returnedObject the returnedObject from the method invocation to check
* @throws AccessDeniedException if access is not granted
*/
default void verify(Supplier<Authentication> authentication, T object, Object returnedObject) {
AuthorizationDecision decision = check(authentication, object, returnedObject);
if (decision != null && !decision.isGranted()) {
throw new AccessDeniedException("Access Denied");
}
}
/**
* Determines if access is granted for a specific authentication and returnedObject.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param object the {@code T} object to check, typically a {@link MethodInvocation}
* @param returnedObject the returned object from the method invocation to check
* @return an {@link AuthorizationDecision} or null if no decision could be made
*/
@Nullable
AuthorizationDecision check(Supplier<Authentication> authentication, T object, Object returnedObject);
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2002-2021 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.authorization.method;
import java.util.function.Supplier;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
/**
* Adapts an {@link AuthorizationManager} into an {@link AfterMethodAuthorizationManager}
*
* @param <T> the {@code T} object to authorize, typically a {@link MethodInvocation}
* @author Josh Cummings
* @since 5.5
*/
public final class AfterMethodAuthorizationManagerAdapter<T> implements AfterMethodAuthorizationManager<T> {
private final AuthorizationManager<T> authorizationManager;
/**
* Construct a {@link AfterMethodAuthorizationManagerAdapter} with the provided
* parameters
* @param authorizationManager the {@link AuthorizationManager} to adapt
*/
public AfterMethodAuthorizationManagerAdapter(AuthorizationManager<T> authorizationManager) {
this.authorizationManager = authorizationManager;
}
/**
* Determine if access is granted for a specific authentication and {@code T} object.
*
* Note that the {@code returnedObject} parameter is ignored
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param object the {@code T} object to check, typically a {@link MethodInvocation}
* @param returnedObject the returned object from the method invocation, ignored in
* this implementation
* @return an {@link AuthorizationDecision} or null if no decision could be made
*/
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, T object, Object returnedObject) {
return this.authorizationManager.check(authentication, object);
}
}

View File

@@ -39,14 +39,15 @@ public final class AuthorizationManagerMethodAfterAdvice<T> implements Authoriza
private final Pointcut pointcut;
private final AuthorizationManager<T> authorizationManager;
private final AfterMethodAuthorizationManager<T> authorizationManager;
/**
* Creates an instance.
* @param pointcut the {@link Pointcut} to use
* @param authorizationManager the {@link AuthorizationManager} to use
*/
public AuthorizationManagerMethodAfterAdvice(Pointcut pointcut, AuthorizationManager<T> authorizationManager) {
public AuthorizationManagerMethodAfterAdvice(Pointcut pointcut,
AfterMethodAuthorizationManager<T> authorizationManager) {
Assert.notNull(pointcut, "pointcut cannot be null");
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
this.pointcut = pointcut;
@@ -57,13 +58,14 @@ public final class AuthorizationManagerMethodAfterAdvice<T> implements Authoriza
* Determines if an {@link Authentication} has access to the {@link T} object using
* the {@link AuthorizationManager}.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param object the {@link T} object to check
* @param object the {@link T} object to check - note that {@code T} should contain
* the returned object
* @throws AccessDeniedException if access is not granted
*/
@Override
public Object after(Supplier<Authentication> authentication, T object, Object returnedObject) {
this.authorizationManager.verify(authentication, object);
return returnedObject;
public Object after(Supplier<Authentication> authentication, T context, Object object) {
this.authorizationManager.verify(authentication, context, object);
return object;
}
/**

View File

@@ -27,9 +27,9 @@ import org.springframework.aop.framework.AopInfrastructureBean;
import org.springframework.security.core.Authentication;
/**
* An {@link Advice} which can determine if an {@link Authentication} has
* access to the returned object from the {@link MethodInvocation}. {@link #getPointcut()}
* describes when the advice applies for the method.
* An {@link Advice} which can determine if an {@link Authentication} has access to the
* returned object from the {@link MethodInvocation}. {@link #getPointcut()} describes
* when the advice applies for the method.
*
* @param <T> the type of object that the authorization check is being done one.
* @author Evgeniy Cheban

View File

@@ -38,21 +38,19 @@ import org.springframework.util.Assert;
* @author Josh Cummings
* @since 5.5
*/
public final class DelegatingAuthorizationMethodAfterAdvice
implements AuthorizationMethodAfterAdvice<MethodAuthorizationContext> {
public final class DelegatingAuthorizationMethodAfterAdvice<T> implements AuthorizationMethodAfterAdvice<T> {
private final Log logger = LogFactory.getLog(getClass());
private final Pointcut pointcut;
private final List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates;
private final List<AuthorizationMethodAfterAdvice<T>> delegates;
/**
* Creates an instance.
* @param delegates the {@link AuthorizationMethodAfterAdvice}s to use
*/
public DelegatingAuthorizationMethodAfterAdvice(
List<AuthorizationMethodAfterAdvice<MethodAuthorizationContext>> delegates) {
public DelegatingAuthorizationMethodAfterAdvice(List<AuthorizationMethodAfterAdvice<T>> delegates) {
Assert.notEmpty(delegates, "delegates cannot be empty");
this.delegates = delegates;
ComposablePointcut pointcut = null;
@@ -79,26 +77,24 @@ public final class DelegatingAuthorizationMethodAfterAdvice
* Delegates to specific {@link AuthorizationMethodAfterAdvice}s and returns the
* <code>returnedObject</code> (possibly modified) from the method argument.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
* @param object the {@link MethodAuthorizationContext} to check
* @param returnedObject the returned object from the {@link MethodInvocation} to
* check
* @return the <code>returnedObject</code> (possibly modified) from the method
* argument
*/
@Override
public Object after(Supplier<Authentication> authentication, MethodAuthorizationContext methodAuthorizationContext,
Object returnedObject) {
public Object after(Supplier<Authentication> authentication, T object, Object returnedObject) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(
LogMessage.format("Post Authorizing %s from %s", returnedObject, methodAuthorizationContext));
this.logger.trace(LogMessage.format("Post Authorizing %s from %s", returnedObject, object));
}
Object result = returnedObject;
for (AuthorizationMethodAfterAdvice<MethodAuthorizationContext> delegate : this.delegates) {
for (AuthorizationMethodAfterAdvice<T> delegate : this.delegates) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Checking authorization on %s from %s using %s", result,
methodAuthorizationContext, delegate));
this.logger.trace(
LogMessage.format("Checking authorization on %s from %s using %s", result, object, delegate));
}
result = delegate.after(authentication, methodAuthorizationContext, result);
result = delegate.after(authentication, object, result);
}
return result;
}

View File

@@ -38,21 +38,19 @@ import org.springframework.util.Assert;
* @author Josh Cummings
* @since 5.5
*/
public final class DelegatingAuthorizationMethodBeforeAdvice
implements AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> {
public final class DelegatingAuthorizationMethodBeforeAdvice<T> implements AuthorizationMethodBeforeAdvice<T> {
private final Log logger = LogFactory.getLog(getClass());
private final Pointcut pointcut;
private final List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates;
private final List<AuthorizationMethodBeforeAdvice<T>> delegates;
/**
* Creates an instance.
* @param delegates the {@link AuthorizationMethodBeforeAdvice}s to use
*/
public DelegatingAuthorizationMethodBeforeAdvice(
List<AuthorizationMethodBeforeAdvice<MethodAuthorizationContext>> delegates) {
public DelegatingAuthorizationMethodBeforeAdvice(List<AuthorizationMethodBeforeAdvice<T>> delegates) {
Assert.notEmpty(delegates, "delegates cannot be empty");
this.delegates = delegates;
ComposablePointcut pointcut = null;
@@ -80,19 +78,18 @@ public final class DelegatingAuthorizationMethodBeforeAdvice
* if all {@link AuthorizationMethodBeforeAdvice}s granted or abstained. Denies only
* if one of the {@link AuthorizationMethodBeforeAdvice}s denied.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
* @param object the {@link MethodAuthorizationContext} to check
*/
@Override
public void before(Supplier<Authentication> authentication, MethodAuthorizationContext methodAuthorizationContext) {
public void before(Supplier<Authentication> authentication, T object) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Pre Authorizing %s", methodAuthorizationContext));
this.logger.trace(LogMessage.format("Pre Authorizing %s", object));
}
for (AuthorizationMethodBeforeAdvice<MethodAuthorizationContext> delegate : this.delegates) {
for (AuthorizationMethodBeforeAdvice<T> delegate : this.delegates) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Checking authorization on %s using %s", methodAuthorizationContext,
delegate));
this.logger.trace(LogMessage.format("Checking authorization on %s using %s", object, delegate));
}
delegate.before(authentication, methodAuthorizationContext);
delegate.before(authentication, object);
}
}

View File

@@ -31,8 +31,6 @@ public final class MethodAuthorizationContext {
private final Class<?> targetClass;
private Object returnObject;
/**
* Creates an instance.
* @param methodInvocation the {@link MethodInvocation} to use
@@ -59,26 +57,10 @@ public final class MethodAuthorizationContext {
return this.targetClass;
}
/**
* Returns the returned object from the {@link MethodInvocation}.
* @return the returned object from the {@link MethodInvocation} to use
*/
public Object getReturnObject() {
return this.returnObject;
}
/**
* Sets the returned object from the {@link MethodInvocation}.
* @param returnObject the returned object from the {@link MethodInvocation} to use
*/
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
@Override
public String toString() {
return "MethodAuthorizationContext[methodInvocation=" + this.methodInvocation + ", targetClass="
+ this.targetClass + ", returnObject=" + this.returnObject + ']';
+ this.targetClass + ']';
}
}

View File

@@ -43,7 +43,8 @@ import org.springframework.util.Assert;
* @author Evgeniy Cheban
* @since 5.5
*/
public final class PostAuthorizeAuthorizationManager implements AuthorizationManager<MethodAuthorizationContext> {
public final class PostAuthorizeAuthorizationManager
implements AfterMethodAuthorizationManager<MethodAuthorizationContext> {
private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry();
@@ -68,14 +69,14 @@ public final class PostAuthorizeAuthorizationManager implements AuthorizationMan
*/
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication,
MethodAuthorizationContext methodAuthorizationContext) {
MethodAuthorizationContext methodAuthorizationContext, Object returnedObject) {
ExpressionAttribute attribute = this.registry.getAttribute(methodAuthorizationContext);
if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) {
return null;
}
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(),
methodAuthorizationContext.getMethodInvocation());
this.expressionHandler.setReturnObject(methodAuthorizationContext.getReturnObject(), ctx);
this.expressionHandler.setReturnObject(returnedObject, ctx);
boolean granted = ExpressionUtils.evaluateAsBoolean(attribute.getExpression(), ctx);
return new AuthorizationDecision(granted);
}

View File

@@ -82,7 +82,6 @@ public final class PostFilterAuthorizationMethodAfterAdvice
* evaluating an expression from the {@link PostFilter} annotation.
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param methodAuthorizationContext the {@link MethodAuthorizationContext} to check
* @param returnedObject the returned object from the {@link MethodInvocation} to
* check
* @return filtered <code>returnedObject</code> from the {@link MethodInvocation}
*/
@@ -98,9 +97,7 @@ public final class PostFilterAuthorizationMethodAfterAdvice
}
EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication.get(),
methodAuthorizationContext.getMethodInvocation());
Object result = this.expressionHandler.filter(returnedObject, attribute.getExpression(), ctx);
methodAuthorizationContext.setReturnObject(result);
return result;
return this.expressionHandler.filter(returnedObject, attribute.getExpression(), ctx);
}
private final class PostFilterExpressionAttributeRegistry