Add tests for AuthorizeReturnObject

See gh-253
This commit is contained in:
Josh Cummings
2024-11-15 15:56:28 -07:00
committed by Moritz Halbritter
parent 7bf63cd4ac
commit 8614b25139
6 changed files with 215 additions and 16 deletions

View File

@@ -0,0 +1,53 @@
package com.example.security.method;
import java.time.Duration;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
import org.springframework.aot.smoketest.support.assertj.AssertableOutput;
import org.springframework.aot.smoketest.support.junit.ApplicationTest;
import static org.assertj.core.api.Assertions.assertThat;
@ApplicationTest
public class AuthorizedObjectTests {
@Test
void anonymousCanOnlyCallPermittedMethods(AssertableOutput output) {
Awaitility.await().atMost(Duration.ofSeconds(10)).untilAsserted(() -> assertThat(output)
// @formatter:off
.hasSingleLineContaining("testAuthorizedObject(): object.getUserProperty() correctly allowed as anonymous")
.hasSingleLineContaining("testAuthorizedObject(): object.getAdminProperty() correctly allowed as anonymous")
.hasSingleLineContaining("testAuthorizedObject(): object.getUserPropertyJsr250() correctly allowed as anonymous")
.hasSingleLineContaining("testAuthorizedObject(): object.getUserPropertySecured() correctly allowed as anonymous")
.hasSingleLineContaining("testAuthorizedObject(): authorized.getUserProperty() correctly denied as anonymous")
.hasSingleLineContaining("testAuthorizedObject(): authorized.getAdminProperty() correctly denied as anonymous")
.hasSingleLineContaining("testAuthorizedObject(): authorized.getUserPropertyJsr250() correctly denied as anonymous")
.hasSingleLineContaining("testAuthorizedObject(): authorized.getUserPropertySecured() correctly denied as anonymous"));
// @formatter:on
}
@Test
void userCanOnlyCallPermittedMethods(AssertableOutput output) {
Awaitility.await().atMost(Duration.ofSeconds(10)).untilAsserted(() -> assertThat(output)
// @formatter:off
.hasSingleLineContaining("testAuthorizedObject(): authorized.getUserProperty() correctly allowed as user")
.hasSingleLineContaining("testAuthorizedObject(): authorized.getAdminProperty() correctly denied as user")
.hasSingleLineContaining("testAuthorizedObject(): authorized.getUserPropertyJsr250() correctly allowed as user")
.hasSingleLineContaining("testAuthorizedObject(): authorized.getUserPropertySecured() correctly allowed as user"));
// @formatter:on
}
@Test
void adminCanOnlyCallPermittedMethods(AssertableOutput output) {
Awaitility.await().atMost(Duration.ofSeconds(10)).untilAsserted(() -> assertThat(output)
// @formatter:off
.hasSingleLineContaining("testAuthorizedObject(): authorized.getUserProperty() correctly denied as admin")
.hasSingleLineContaining("testAuthorizedObject(): authorized.getAdminProperty() correctly allowed as admin")
.hasSingleLineContaining("testAuthorizedObject(): authorized.getUserPropertyJsr250() correctly denied as admin")
.hasSingleLineContaining("testAuthorizedObject(): authorized.getUserPropertySecured() correctly denied as admin"));
// @formatter:on
}
}

View File

@@ -0,0 +1,31 @@
package com.example.security.method;
import jakarta.annotation.security.RolesAllowed;
import org.springframework.security.access.annotation.Secured;
public class AuthorizableObject {
private final String property = "value";
@HasRole("ADMIN")
public String getAdminProperty() {
return this.property;
}
@HasRole("USER")
public String getUserProperty() {
return this.property;
}
@RolesAllowed("USER")
public String getUserPropertyJsr250() {
return this.property;
}
@Secured("ROLE_USER")
public String getUserPropertySecured() {
return this.property;
}
}

View File

@@ -0,0 +1,18 @@
package com.example.security.method;
import org.springframework.security.authorization.method.AuthorizeReturnObject;
import org.springframework.stereotype.Service;
@Service
public class AuthorizedReturnValueService {
@AuthorizeReturnObject
public AuthorizableObject getAuthorizedObject() {
return new AuthorizableObject();
}
public AuthorizableObject getObject() {
return new AuthorizableObject();
}
}

View File

@@ -17,11 +17,14 @@
package com.example.security.method;
import java.util.List;
import java.util.function.Supplier;
import org.springframework.boot.CommandLineRunner;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.UserDetailsManager;
@@ -38,15 +41,19 @@ public class CLR implements CommandLineRunner {
private final PostAuthorizeProtectedService postAuthorizeProtectedService;
private final AuthorizedReturnValueService authorizedReturnValueService;
private final UserDetailsManager userDetailsManager;
public CLR(PreAuthorizeProtectedService preAuthorizeProtectedService,
SecuredProtectedService securedProtectedService, Jsr250ProtectedService jsr250ProtectedService,
PostAuthorizeProtectedService postAuthorizeProtectedService, UserDetailsManager userDetailsManager) {
PostAuthorizeProtectedService postAuthorizeProtectedService,
AuthorizedReturnValueService authorizedReturnValueService, UserDetailsManager userDetailsManager) {
this.preAuthorizeProtectedService = preAuthorizeProtectedService;
this.securedProtectedService = securedProtectedService;
this.jsr250ProtectedService = jsr250ProtectedService;
this.postAuthorizeProtectedService = postAuthorizeProtectedService;
this.authorizedReturnValueService = authorizedReturnValueService;
this.userDetailsManager = userDetailsManager;
}
@@ -66,35 +73,36 @@ public class CLR implements CommandLineRunner {
testJsr250Admin();
testJsr250PermitAll();
testJsr250DenyAll();
testAuthorizedObject();
}
private void testAnonymous() {
impersonateAnonymous();
impersonateNoAuthentication();
this.preAuthorizeProtectedService.anonymous();
System.out.println("testAnonymous(): preAuthorizeProtectedService.anonymous() worked as anonymous");
}
private void testPostAuthorizeAnonymous() {
impersonateAnonymous();
impersonateNoAuthentication();
this.postAuthorizeProtectedService.anonymous();
System.out
.println("testPostAuthorizeAnonymous(): postAuthorizeProtectedService.anonymous() worked as anonymous");
}
private void testSecuredAnonymous() {
impersonateAnonymous();
impersonateNoAuthentication();
this.securedProtectedService.anonymous();
System.out.println("testSecuredAnonymous(): securedProtectedService.anonymous() worked as anonymous");
}
private void testJsr250Anonymous() {
impersonateAnonymous();
impersonateNoAuthentication();
this.jsr250ProtectedService.anonymous();
System.out.println("testJsr250Anonymous(): jsr250ProtectedService.anonymous() worked as anonymous");
}
private void testUser() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.preAuthorizeProtectedService.user();
throw new IllegalStateException("testUser(): preAuthorizeProtectedService.user() worked as anonymous");
@@ -109,7 +117,7 @@ public class CLR implements CommandLineRunner {
}
private void testPostAuthorizeUser() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.postAuthorizeProtectedService.user();
throw new IllegalStateException(
@@ -125,7 +133,7 @@ public class CLR implements CommandLineRunner {
}
private void testSecuredUser() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.securedProtectedService.user();
throw new IllegalStateException("testSecuredUser(): securedProtectedService.user() worked as anonymous");
@@ -140,7 +148,7 @@ public class CLR implements CommandLineRunner {
}
private void testJsr250User() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.jsr250ProtectedService.user();
throw new IllegalStateException("testJsr250User(): jsr250ProtectedService.user() worked as anonymous");
@@ -155,7 +163,7 @@ public class CLR implements CommandLineRunner {
}
private void testAdmin() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.preAuthorizeProtectedService.admin();
throw new IllegalStateException("testAdmin(): preAuthorizeProtectedService.admin() worked as anonymous");
@@ -179,7 +187,7 @@ public class CLR implements CommandLineRunner {
}
private void testPostAuthorizeAdmin() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.postAuthorizeProtectedService.admin();
throw new IllegalStateException(
@@ -205,7 +213,7 @@ public class CLR implements CommandLineRunner {
}
private void testSecuredAdmin() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.securedProtectedService.admin();
throw new IllegalStateException("testSecuredAdmin(): securedProtectedService.admin() worked as anonymous");
@@ -229,7 +237,7 @@ public class CLR implements CommandLineRunner {
}
private void testJsr250Admin() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.jsr250ProtectedService.admin();
throw new IllegalStateException("testJsr250Admin(): jsr250ProtectedService.admin() worked as anonymous");
@@ -253,7 +261,7 @@ public class CLR implements CommandLineRunner {
}
private void testJsr250DenyAll() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.jsr250ProtectedService.denyAll();
throw new IllegalStateException(
@@ -283,7 +291,7 @@ public class CLR implements CommandLineRunner {
}
private void testJsr250PermitAll() {
impersonateAnonymous();
impersonateNoAuthentication();
this.jsr250ProtectedService.permitAll();
System.out.println("testJsr250PermitAll(): jsr250ProtectedService.permitAll() worked as anonymous");
@@ -296,10 +304,53 @@ public class CLR implements CommandLineRunner {
System.out.println("testJsr250PermitAll(): jsr250ProtectedService.permitAll() worked as admin");
}
private void impersonateAnonymous() {
private void testAuthorizedObject() {
AuthorizableObject object = this.authorizedReturnValueService.getObject();
AuthorizableObject authorized = this.authorizedReturnValueService.getAuthorizedObject();
impersonateAnonymous();
expectAllow(object::getAdminProperty).report("testAuthorizedObject", "object.getAdminProperty", "anonymous");
expectAllow(object::getUserProperty).report("testAuthorizedObject", "object.getUserProperty", "anonymous");
expectAllow(object::getUserPropertyJsr250).report("testAuthorizedObject", "object.getUserPropertyJsr250",
"anonymous");
expectAllow(object::getUserPropertySecured).report("testAuthorizedObject", "object.getUserPropertySecured",
"anonymous");
expectDeny(authorized::getAdminProperty).report("testAuthorizedObject", "authorized.getAdminProperty",
"anonymous");
expectDeny(authorized::getUserProperty).report("testAuthorizedObject", "authorized.getUserProperty",
"anonymous");
expectDeny(authorized::getUserPropertyJsr250).report("testAuthorizedObject", "authorized.getUserPropertyJsr250",
"anonymous");
expectDeny(authorized::getUserPropertySecured).report("testAuthorizedObject",
"authorized.getUserPropertySecured", "anonymous");
impersonateUser();
expectDeny(authorized::getAdminProperty).report("testAuthorizedObject", "authorized.getAdminProperty", "user");
expectAllow(authorized::getUserProperty).report("testAuthorizedObject", "authorized.getUserProperty", "user");
expectAllow(authorized::getUserPropertyJsr250).report("testAuthorizedObject",
"authorized.getUserPropertyJsr250", "user");
expectAllow(authorized::getUserPropertySecured).report("testAuthorizedObject",
"authorized.getUserPropertySecured", "user");
impersonateAdmin();
expectAllow(authorized::getAdminProperty).report("testAuthorizedObject", "authorized.getAdminProperty",
"admin");
expectDeny(authorized::getUserProperty).report("testAuthorizedObject", "authorized.getUserProperty", "admin");
expectDeny(authorized::getUserPropertyJsr250).report("testAuthorizedObject", "authorized.getUserPropertyJsr250",
"admin");
expectDeny(authorized::getUserPropertySecured).report("testAuthorizedObject",
"authorized.getUserPropertySecured", "admin");
}
private void impersonateNoAuthentication() {
SecurityContextHolder.getContext().setAuthentication(null);
}
private void impersonateAnonymous() {
SecurityContextHolder.getContext()
.setAuthentication(new TestingAuthenticationToken("principal", "credentials"));
}
private void impersonateUser() {
UserDetails user = this.userDetailsManager.loadUserByUsername("user");
SecurityContextHolder.getContext()
@@ -314,4 +365,30 @@ public class CLR implements CommandLineRunner {
new TestingAuthenticationToken(user, user.getPassword(), List.copyOf(user.getAuthorities())));
}
private interface ResultReporter {
void report(String testMethod, String method, String role);
}
private ResultReporter expectAllow(Supplier<?> test) {
try {
test.get();
return (t, m, r) -> System.out.printf("%s(): %s() correctly allowed as %s%n", t, m, r);
}
catch (AccessDeniedException ex) {
return (t, m, r) -> System.out.printf("%s(): %s() incorrectly denied as %s%n", t, m, r);
}
}
private ResultReporter expectDeny(Supplier<?> test) {
try {
test.get();
return (t, m, r) -> System.out.printf("%s(): %s() incorrectly allowed as %s%n", t, m, r);
}
catch (AccessDeniedException ex) {
return (t, m, r) -> System.out.printf("%s(): %s() correctly denied as %s%n", t, m, r);
}
}
}

View File

@@ -0,0 +1,14 @@
package com.example.security.method;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.springframework.security.access.prepost.PreAuthorize;
@Retention(RetentionPolicy.RUNTIME)
@PreAuthorize("hasRole('{value}')")
public @interface HasRole {
String value();
}

View File

@@ -19,6 +19,7 @@ package com.example.security.method;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@@ -28,6 +29,11 @@ import org.springframework.security.provisioning.UserDetailsManager;
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
class SecurityConfiguration {
@Bean
static AnnotationTemplateExpressionDefaults annotationTemplateExpressionDefaults() {
return new AnnotationTemplateExpressionDefaults();
}
@Bean
@SuppressWarnings("deprecation")
UserDetailsManager userDetailsManager() {