Skip to content

Commit

Permalink
Add AuthorizationResult Support to AuthorizationManager
Browse files Browse the repository at this point in the history
Closes gh-14843
  • Loading branch information
franticticktick committed Sep 26, 2024
1 parent 3b20844 commit e014a6a
Show file tree
Hide file tree
Showing 20 changed files with 181 additions and 109 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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 Down Expand Up @@ -34,6 +34,7 @@
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
Expand Down Expand Up @@ -221,7 +222,7 @@ private boolean checkLoginPageIsPublic(List<Filter> filters, FilterInvocation lo
AuthorizationManager<HttpServletRequest> authorizationManager = authorizationFilter
.getAuthorizationManager();
try {
AuthorizationDecision decision = authorizationManager.check(() -> TEST, loginRequest.getHttpRequest());
AuthorizationResult decision = authorizationManager.authorize(() -> TEST, loginRequest.getHttpRequest());
return decision != null && decision.isGranted();
}
catch (Exception ex) {
Expand Down Expand Up @@ -252,7 +253,7 @@ private Supplier<Boolean> deriveAnonymousCheck(List<Filter> filters, FilterInvoc
return () -> {
AuthorizationManager<HttpServletRequest> authorizationManager = authorizationFilter
.getAuthorizationManager();
AuthorizationDecision decision = authorizationManager.check(() -> token, loginRequest.getHttpRequest());
AuthorizationResult decision = authorizationManager.authorize(() -> token, loginRequest.getHttpRequest());
return decision != null && decision.isGranted();
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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 Down Expand Up @@ -90,13 +90,13 @@ public void getWhenUsingAuthorizationManagerThenRedirectsToLogin() throws Except
this.spring.configLocations(this.xml("AuthorizationManager")).autowire();
AuthorizationManager<HttpServletRequest> authorizationManager = this.spring.getContext()
.getBean(AuthorizationManager.class);
given(authorizationManager.check(any(), any())).willReturn(new AuthorizationDecision(false));
given(authorizationManager.authorize(any(), any())).willReturn(new AuthorizationDecision(false));
// @formatter:off
this.mvc.perform(get("/"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("http://localhost/login"));
// @formatter:on
verify(authorizationManager).check(any(), any());
verify(authorizationManager).authorize(any(), any());
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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 Down Expand Up @@ -42,8 +42,26 @@ public interface AuthorizationEventPublisher {
* @param object the secured object
* @param decision the decision about whether the user may access the secured object
* @param <T> the secured object's type
* @deprecated use {@link #publishAuthorizationEvent(Supplier, Object, AuthorizationResult)} instead
*/
@Deprecated
<T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision);

/**
* Publish the given details in the form of an event, typically
* {@link AuthorizationGrantedEvent} or {@link AuthorizationDeniedEvent}.
*
* Note that success events can be very noisy if enabled by default. Because of this
* implementations may choose to drop success events by default.
* @param authentication a {@link Supplier} for the current user
* @param object the secured object
* @param decision {@link AuthorizationResult} the decision about whether the user may access the secured object
* @param <T> the secured object's type
* @since 6.4
*/
default <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object,
AuthorizationResult decision) {
publishAuthorizationEvent(authentication, object,(AuthorizationDecision) decision);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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 Down Expand Up @@ -50,8 +50,20 @@ default void verify(Supplier<Authentication> authentication, T object) {
* @param authentication the {@link Supplier} of the {@link Authentication} to check
* @param object the {@link T} object to check
* @return an {@link AuthorizationDecision} or null if no decision could be made
* @deprecated please use {@link #authorize(Supplier, Object)} instead
*/
@Nullable
@Deprecated
AuthorizationDecision check(Supplier<Authentication> authentication, T object);

/**
* Determines if access is granted for a specific authentication and object.
* @param authentication the {@link Supplier} of the {@link Authentication} to authorize
* @param object the {@link T} object to authorize
* @return an {@link AuthorizationResult}
* @since 6.4
*/
default AuthorizationResult authorize(Supplier<Authentication> authentication, T object){
return check(authentication, object);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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 Down Expand Up @@ -33,7 +33,7 @@ public class AuthorizationObservationContext<T> extends Observation.Context {

private final T object;

private AuthorizationDecision decision;
private AuthorizationResult authorizationResult;

public AuthorizationObservationContext(T object) {
Assert.notNull(object, "object cannot be null");
Expand Down Expand Up @@ -73,15 +73,32 @@ public T getObject() {
* @return the observed {@link AuthorizationDecision}
*/
public AuthorizationDecision getDecision() {
return this.decision;
return (AuthorizationDecision) this.authorizationResult;
}

/**
* Set the observed {@link AuthorizationDecision}
* @param decision the observed {@link AuthorizationDecision}
*/
public void setDecision(AuthorizationDecision decision) {
this.decision = decision;
this.authorizationResult = decision;
}

/**
* Get the observed {@link AuthorizationResult}
* @return the observed {@link AuthorizationResult}
* @since 6.4
*/
public AuthorizationResult getAuthorizationResult() {
return this.authorizationResult;
}

/**
* Set the observed {@link AuthorizationResult}
* @param authorizationResult the observed {@link AuthorizationResult}
* @since 6.4
*/
public void setAuthorizationResult(AuthorizationResult authorizationResult) {
this.authorizationResult = authorizationResult;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 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 Down Expand Up @@ -100,10 +100,10 @@ private String getObjectType(AuthorizationObservationContext<?> context) {
}

private String getAuthorizationDecision(AuthorizationObservationContext<?> context) {
if (context.getDecision() == null) {
if (context.getAuthorizationResult() == null) {
return "unknown";
}
return String.valueOf(context.getDecision().isGranted());
return String.valueOf(context.getAuthorizationResult().isGranted());
}

private String getAuthorities(AuthorizationObservationContext<?> context) {
Expand All @@ -114,10 +114,10 @@ private String getAuthorities(AuthorizationObservationContext<?> context) {
}

private String getDecisionDetails(AuthorizationObservationContext<?> context) {
if (context.getDecision() == null) {
if (context.getAuthorizationResult() == null) {
return "unknown";
}
AuthorizationDecision decision = context.getDecision();
AuthorizationResult decision = context.getAuthorizationResult();
return String.valueOf(decision);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.
* 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;

import java.util.function.Supplier;

import org.springframework.security.core.Authentication;

/**
* An {@link AuthorizationEventPublisher} implementation that does not authorization publish events
*
* @author Max Batischev
* @since 6.4
*/
public final class NoopAuthorizationEventPublisher implements AuthorizationEventPublisher {

@Override
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object, AuthorizationDecision decision) {

}

@Override
public <T> void publishAuthorizationEvent(Supplier<Authentication> authentication, T object, AuthorizationResult decision) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.NoopAuthorizationEventPublisher;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
Expand Down Expand Up @@ -60,7 +61,7 @@ public final class AuthorizationManagerAfterMethodInterceptor implements Authori

private int order;

private AuthorizationEventPublisher eventPublisher = AuthorizationManagerAfterMethodInterceptor::noPublish;
private AuthorizationEventPublisher eventPublisher = new NoopAuthorizationEventPublisher();

/**
* Creates an instance.
Expand Down Expand Up @@ -182,7 +183,7 @@ public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy strat
private Object attemptAuthorization(MethodInvocation mi, Object result) {
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
MethodInvocationResult object = new MethodInvocationResult(mi, result);
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, object);
AuthorizationResult decision = this.authorizationManager.authorize(this::getAuthentication, object);
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, object, decision);
if (decision != null && !decision.isGranted()) {
this.logger.debug(LogMessage.of(() -> "Failed to authorize " + mi + " with authorization manager "
Expand All @@ -193,7 +194,7 @@ private Object attemptAuthorization(MethodInvocation mi, Object result) {
return result;
}

private Object handlePostInvocationDenied(MethodInvocationResult mi, AuthorizationDecision decision) {
private Object handlePostInvocationDenied(MethodInvocationResult mi, AuthorizationResult decision) {
if (this.authorizationManager instanceof MethodAuthorizationDeniedHandler deniedHandler) {
return deniedHandler.handleDeniedInvocationResult(mi, decision);
}
Expand All @@ -209,9 +210,4 @@ private Authentication getAuthentication() {
return authentication;
}

private static <T> void noPublish(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision) {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ private boolean isMultiValue(Class<?> returnType, ReactiveAdapter adapter) {

private Mono<Object> postAuthorize(Mono<Authentication> authentication, MethodInvocation mi, Object result) {
MethodInvocationResult invocationResult = new MethodInvocationResult(mi, result);
return this.authorizationManager.check(authentication, invocationResult)
return this.authorizationManager.authorize(authentication, invocationResult)
.switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
.flatMap((decision) -> postProcess(decision, invocationResult));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationDeniedException;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.authorization.AuthorizationResult;
import org.springframework.security.authorization.NoopAuthorizationEventPublisher;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
Expand Down Expand Up @@ -65,7 +65,7 @@ public final class AuthorizationManagerBeforeMethodInterceptor implements Author

private int order = AuthorizationInterceptorsOrder.FIRST.getOrder();

private AuthorizationEventPublisher eventPublisher = AuthorizationManagerBeforeMethodInterceptor::noPublish;
private AuthorizationEventPublisher eventPublisher = new NoopAuthorizationEventPublisher();

/**
* Creates an instance.
Expand Down Expand Up @@ -247,9 +247,9 @@ public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy secur

private Object attemptAuthorization(MethodInvocation mi) throws Throwable {
this.logger.debug(LogMessage.of(() -> "Authorizing method invocation " + mi));
AuthorizationDecision decision;
AuthorizationResult decision;
try {
decision = this.authorizationManager.check(this::getAuthentication, mi);
decision = this.authorizationManager.authorize(this::getAuthentication, mi);
}
catch (AuthorizationDeniedException denied) {
return handle(mi, denied);
Expand Down Expand Up @@ -298,10 +298,4 @@ private Authentication getAuthentication() {
}
return authentication;
}

private static <T> void noPublish(Supplier<Authentication> authentication, T object,
AuthorizationDecision decision) {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ private Flux<Object> preAuthorized(MethodInvocation mi, Flux<Object> mapping) {

private Mono<Object> preAuthorized(MethodInvocation mi, Mono<Object> mapping) {
Mono<Authentication> authentication = ReactiveAuthenticationUtils.getAuthentication();
return this.authorizationManager.check(authentication, mi)
return this.authorizationManager.authorize(authentication, mi)
.switchIfEmpty(Mono.just(new AuthorizationDecision(false)))
.flatMap((decision) -> {
if (decision.isGranted()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 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 Down Expand Up @@ -77,7 +77,7 @@ public void beforeWhenMockAuthorizationManagerThenCheckAndReturnedObject() throw
Pointcut.TRUE, mockAuthorizationManager);
Object returnedObject = advice.invoke(mockMethodInvocation);
assertThat(returnedObject).isEqualTo(result.getResult());
verify(mockAuthorizationManager).check(any(Supplier.class), any(MethodInvocationResult.class));
verify(mockAuthorizationManager).authorize(any(Supplier.class), any(MethodInvocationResult.class));
}

@Test
Expand Down Expand Up @@ -139,15 +139,15 @@ public void invokeWhenAuthorizationEventPublisherThenUses() throws Throwable {

advice.invoke(mockMethodInvocation);
verify(eventPublisher).publishAuthorizationEvent(any(Supplier.class), any(MethodInvocationResult.class),
any(AuthorizationDecision.class));
any(AuthorizationResult.class));
}

@Test
public void invokeWhenCustomAuthorizationDeniedExceptionThenThrows() throws Throwable {
MethodInvocation mi = mock(MethodInvocation.class);
given(mi.proceed()).willReturn("ok");
AuthorizationManager<MethodInvocationResult> manager = mock(AuthorizationManager.class);
given(manager.check(any(), any()))
given(manager.authorize(any(), any()))
.willThrow(new MyAuthzDeniedException("denied", new AuthorizationDecision(false)));
AuthorizationManagerAfterMethodInterceptor advice = new AuthorizationManagerAfterMethodInterceptor(
Pointcut.TRUE, manager);
Expand Down
Loading

0 comments on commit e014a6a

Please sign in to comment.