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

AuthorizationManager should return AuthorizationResult #14846

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
@@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though it's a default implementation, we should perform a type-safe check. Maybe you could do the following:

if (!(result instanceof AuthorizationDecision decision)) {
    throw new UnsupportedOperationException("result must be of type AuthorizationDecision");
}
publishAuthorizationEvent(authentication, object, 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() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's deprecate this as well.

return this.decision;
return (AuthorizationDecision) this.authorizationResult;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please perform a type check here. The reason is so that the application can give a more informative error than ClassCastException. Maybe something like:

Assert.isInstanceOf(AuthorizationDecision.class, "please call getAuthorizationResult instead. If you must call getDecision, please ensure that the result you provide is of type AuthorizationDecision");
return (AuthorizationDecision) this.authorizationResult;

}

/**
* Set the observed {@link AuthorizationDecision}
* @param decision the observed {@link AuthorizationDecision}
*/
public void setDecision(AuthorizationDecision decision) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's deprecate this as well

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not introduce this in this PR. If you are able to make this package-private, I don't mind leaving it. Otherwise, let's stick with private inner classes for now, even though that's a bit of duplication.


@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
Loading