Skip to content

Commit

Permalink
Add tests for AuthorizeReturnObject
Browse files Browse the repository at this point in the history
See gh-253
  • Loading branch information
jzheaux authored and mhalbritter committed Jan 31, 2025
1 parent 7bf63cd commit 8614b25
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -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
}

}
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
@@ -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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

Expand All @@ -66,35 +73,36 @@ public void run(String... args) {
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");
Expand All @@ -109,7 +117,7 @@ private void testUser() {
}

private void testPostAuthorizeUser() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.postAuthorizeProtectedService.user();
throw new IllegalStateException(
Expand All @@ -125,7 +133,7 @@ private void testPostAuthorizeUser() {
}

private void testSecuredUser() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.securedProtectedService.user();
throw new IllegalStateException("testSecuredUser(): securedProtectedService.user() worked as anonymous");
Expand All @@ -140,7 +148,7 @@ private void testSecuredUser() {
}

private void testJsr250User() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.jsr250ProtectedService.user();
throw new IllegalStateException("testJsr250User(): jsr250ProtectedService.user() worked as anonymous");
Expand All @@ -155,7 +163,7 @@ private void testJsr250User() {
}

private void testAdmin() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.preAuthorizeProtectedService.admin();
throw new IllegalStateException("testAdmin(): preAuthorizeProtectedService.admin() worked as anonymous");
Expand All @@ -179,7 +187,7 @@ private void testAdmin() {
}

private void testPostAuthorizeAdmin() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.postAuthorizeProtectedService.admin();
throw new IllegalStateException(
Expand All @@ -205,7 +213,7 @@ private void testPostAuthorizeAdmin() {
}

private void testSecuredAdmin() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.securedProtectedService.admin();
throw new IllegalStateException("testSecuredAdmin(): securedProtectedService.admin() worked as anonymous");
Expand All @@ -229,7 +237,7 @@ private void testSecuredAdmin() {
}

private void testJsr250Admin() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.jsr250ProtectedService.admin();
throw new IllegalStateException("testJsr250Admin(): jsr250ProtectedService.admin() worked as anonymous");
Expand All @@ -253,7 +261,7 @@ private void testJsr250Admin() {
}

private void testJsr250DenyAll() {
impersonateAnonymous();
impersonateNoAuthentication();
try {
this.jsr250ProtectedService.denyAll();
throw new IllegalStateException(
Expand Down Expand Up @@ -283,7 +291,7 @@ private void testJsr250DenyAll() {
}

private void testJsr250PermitAll() {
impersonateAnonymous();
impersonateNoAuthentication();
this.jsr250ProtectedService.permitAll();
System.out.println("testJsr250PermitAll(): jsr250ProtectedService.permitAll() worked as anonymous");

Expand All @@ -296,10 +304,53 @@ private void testJsr250PermitAll() {
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()
Expand All @@ -314,4 +365,30 @@ private void impersonateAdmin() {
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);
}
}

}
Original file line number Diff line number Diff line change
@@ -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();

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
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;
Expand All @@ -28,6 +29,11 @@
@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)
class SecurityConfiguration {

@Bean
static AnnotationTemplateExpressionDefaults annotationTemplateExpressionDefaults() {
return new AnnotationTemplateExpressionDefaults();
}

@Bean
@SuppressWarnings("deprecation")
UserDetailsManager userDetailsManager() {
Expand Down

0 comments on commit 8614b25

Please sign in to comment.