Move Core Access API

Issue gh-17847
This commit is contained in:
Josh Cummings 2025-08-28 11:00:46 -06:00
parent 3a1692f3c3
commit eedcec9d5c
No known key found for this signature in database
GPG Key ID: 869B37A20E876129
157 changed files with 805 additions and 38 deletions

View File

@ -0,0 +1,42 @@
apply plugin: 'io.spring.convention.spring-module'
dependencies {
management platform(project(":spring-security-dependencies"))
api project(':spring-security-crypto')
api project(':spring-security-core')
api 'org.springframework:spring-aop'
api 'org.springframework:spring-beans'
api 'org.springframework:spring-context'
api 'org.springframework:spring-core'
api 'org.springframework:spring-expression'
api 'io.micrometer:micrometer-observation'
optional 'com.fasterxml.jackson.core:jackson-databind'
optional 'io.micrometer:context-propagation'
optional 'io.projectreactor:reactor-core'
optional 'jakarta.annotation:jakarta.annotation-api'
optional 'org.aspectj:aspectjrt'
optional 'org.springframework:spring-jdbc'
optional 'org.springframework:spring-tx'
optional 'org.jetbrains.kotlinx:kotlinx-coroutines-reactor'
testImplementation 'commons-collections:commons-collections'
testImplementation 'io.projectreactor:reactor-test'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-params"
testImplementation "org.junit.jupiter:junit-jupiter-engine"
testImplementation "org.mockito:mockito-core"
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-core-test"
testImplementation "org.springframework:spring-test"
testImplementation 'org.skyscreamer:jsonassert'
testImplementation 'org.springframework:spring-test'
testImplementation 'org.jetbrains.kotlin:kotlin-reflect'
testImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
testImplementation 'io.mockk:mockk'
testRuntimeOnly 'org.hsqldb:hsqldb'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

View File

@ -0,0 +1,23 @@
/*
* Copyright 2004-present 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.
*/
/**
* Support for JSR-250 and Spring Security {@code @Secured} annotations.
*/
@NullMarked
package org.springframework.security.access.annotation;
import org.jspecify.annotations.NullMarked;

View File

@ -0,0 +1,25 @@
/*
* Copyright 2004-present 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.
*/
/**
* Implementation of expression-based method security.
*
* @since 3.0
*/
@NullMarked
package org.springframework.security.access.expression.method;
import org.jspecify.annotations.NullMarked;

View File

@ -0,0 +1,27 @@
/*
* Copyright 2004-present 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.
*/
/**
* Core access-control related code, including security metadata related classes,
* interception code, access control annotations, EL support and voter-based
* implementations of the central
* {@link org.springframework.security.access.AccessDecisionManager AccessDecisionManager}
* interface.
*/
@NullMarked
package org.springframework.security.access;
import org.jspecify.annotations.NullMarked;

View File

@ -0,0 +1,27 @@
/*
* Copyright 2004-present 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.
*/
/**
* Contains the infrastructure classes for handling the {@code @PreAuthorize},
* {@code @PreFilter}, {@code @PostAuthorize} and {@code @PostFilter} annotations.
* <p>
* Other than the annotations themselves, the classes should be regarded as for internal
* framework use and are liable to change without notice.
*/
@NullMarked
package org.springframework.security.access.prepost;
import org.jspecify.annotations.NullMarked;

View File

@ -0,0 +1,36 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.access;
/**
* Represents the interface of a secured object.
*
* @author Ben Alex
*/
public interface ITargetObject {
Integer computeHashCode(String input);
int countLength(String input);
String makeLowerCase(String input);
String makeUpperCase(String input);
String publicMakeLowerCase(String input);
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.access;
/**
* Simply extends {@link TargetObject} so we have a different object to put configuration
* attributes against.
* <P>
* There is no different behaviour. We have to define each method so that
* <code>Class.getMethod(methodName, args)</code> returns a <code>Method</code>
* referencing this class rather than the parent class.
* </p>
* <P>
* We need to implement <code>ITargetObject</code> again because the
* <code>MethodDefinitionAttributes</code> only locates attributes on interfaces
* explicitly defined by the intercepted class (not the interfaces defined by its parent
* class or classes).
* </p>
*
* @author Ben Alex
*/
public class OtherTargetObject extends TargetObject implements ITargetObject {
@Override
public String makeLowerCase(String input) {
return super.makeLowerCase(input);
}
@Override
public String makeUpperCase(String input) {
return super.makeUpperCase(input);
}
@Override
public String publicMakeLowerCase(String input) {
return super.publicMakeLowerCase(input);
}
}

View File

@ -0,0 +1,81 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* 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.access;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
/**
* Represents a secured object.
*
* @author Ben Alex
*/
public class TargetObject implements ITargetObject {
@Override
public Integer computeHashCode(String input) {
return input.hashCode();
}
@Override
public int countLength(String input) {
return input.length();
}
/**
* Returns the lowercase string, followed by security environment information.
* @param input the message to make lowercase
* @return the lowercase message, a space, the <code>Authentication</code> class that
* was on the <code>SecurityContext</code> at the time of method invocation, and a
* boolean indicating if the <code>Authentication</code> object is authenticated or
* not
*/
@Override
public String makeLowerCase(String input) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null) {
return input.toLowerCase() + " Authentication empty";
}
else {
return input.toLowerCase() + " " + auth.getClass().getName() + " " + auth.isAuthenticated();
}
}
/**
* Returns the uppercase string, followed by security environment information.
* @param input the message to make uppercase
* @return the uppercase message, a space, the <code>Authentication</code> class that
* was on the <code>SecurityContext</code> at the time of method invocation, and a
* boolean indicating if the <code>Authentication</code> object is authenticated or
* not
*/
@Override
public String makeUpperCase(String input) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return input.toUpperCase() + " " + auth.getClass().getName() + " " + auth.isAuthenticated();
}
/**
* Delegates through to the {@link #makeLowerCase(String)} method.
* @param input the message to be made lower-case
*/
@Override
public String publicMakeLowerCase(String input) {
return this.makeLowerCase(input);
}
}

View File

@ -39,8 +39,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail; import static org.assertj.core.api.Assertions.fail;
/** /**
* Tests for * Tests for {@link SecuredAnnotationSecurityMetadataSource}
* {@link org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource}
* *
* @author Mark St.Godard * @author Mark St.Godard
* @author Joe Scalise * @author Joe Scalise

View File

@ -17,6 +17,7 @@
package org.springframework.security.access.expression.method; package org.springframework.security.access.expression.method;
import org.aopalliance.intercept.MethodInvocation; import org.aopalliance.intercept.MethodInvocation;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -28,7 +29,6 @@ import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolver;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
@ -65,19 +65,19 @@ public class MethodSecurityExpressionRootTests {
public void canCallMethodsOnVariables() { public void canCallMethodsOnVariables() {
this.ctx.setVariable("var", "somestring"); this.ctx.setVariable("var", "somestring");
Expression e = this.parser.parseExpression("#var.length() == 10"); Expression e = this.parser.parseExpression("#var.length() == 10");
assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue(); Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue();
} }
@Test @Test
public void isAnonymousReturnsTrueIfTrustResolverReportsAnonymous() { public void isAnonymousReturnsTrueIfTrustResolverReportsAnonymous() {
given(this.trustResolver.isAnonymous(this.user)).willReturn(true); given(this.trustResolver.isAnonymous(this.user)).willReturn(true);
assertThat(this.root.isAnonymous()).isTrue(); Assertions.assertThat(this.root.isAnonymous()).isTrue();
} }
@Test @Test
public void isAnonymousReturnsFalseIfTrustResolverReportsNonAnonymous() { public void isAnonymousReturnsFalseIfTrustResolverReportsNonAnonymous() {
given(this.trustResolver.isAnonymous(this.user)).willReturn(false); given(this.trustResolver.isAnonymous(this.user)).willReturn(false);
assertThat(this.root.isAnonymous()).isFalse(); Assertions.assertThat(this.root.isAnonymous()).isFalse();
} }
@Test @Test
@ -87,7 +87,7 @@ public class MethodSecurityExpressionRootTests {
this.ctx.setVariable("domainObject", dummyDomainObject); this.ctx.setVariable("domainObject", dummyDomainObject);
this.root.setPermissionEvaluator(pe); this.root.setPermissionEvaluator(pe);
given(pe.hasPermission(this.user, dummyDomainObject, "ignored")).willReturn(false); given(pe.hasPermission(this.user, dummyDomainObject, "ignored")).willReturn(false);
assertThat(this.root.hasPermission(dummyDomainObject, "ignored")).isFalse(); Assertions.assertThat(this.root.hasPermission(dummyDomainObject, "ignored")).isFalse();
} }
@Test @Test
@ -97,7 +97,7 @@ public class MethodSecurityExpressionRootTests {
this.ctx.setVariable("domainObject", dummyDomainObject); this.ctx.setVariable("domainObject", dummyDomainObject);
this.root.setPermissionEvaluator(pe); this.root.setPermissionEvaluator(pe);
given(pe.hasPermission(this.user, dummyDomainObject, "ignored")).willReturn(true); given(pe.hasPermission(this.user, dummyDomainObject, "ignored")).willReturn(true);
assertThat(this.root.hasPermission(dummyDomainObject, "ignored")).isTrue(); Assertions.assertThat(this.root.hasPermission(dummyDomainObject, "ignored")).isTrue();
} }
@Test @Test
@ -109,13 +109,13 @@ public class MethodSecurityExpressionRootTests {
given(pe.hasPermission(eq(this.user), eq(dummyDomainObject), any(Integer.class))).willReturn(true, true, false); given(pe.hasPermission(eq(this.user), eq(dummyDomainObject), any(Integer.class))).willReturn(true, true, false);
Expression e = this.parser.parseExpression("hasPermission(#domainObject, 0xA)"); Expression e = this.parser.parseExpression("hasPermission(#domainObject, 0xA)");
// evaluator returns true // evaluator returns true
assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue(); Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue();
e = this.parser.parseExpression("hasPermission(#domainObject, 10)"); e = this.parser.parseExpression("hasPermission(#domainObject, 10)");
// evaluator returns true // evaluator returns true
assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue(); Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue();
e = this.parser.parseExpression("hasPermission(#domainObject, 0xFF)"); e = this.parser.parseExpression("hasPermission(#domainObject, 0xFF)");
// evaluator returns false, make sure return value matches // evaluator returns false, make sure return value matches
assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isFalse(); Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isFalse();
} }
@Test @Test
@ -132,11 +132,11 @@ public class MethodSecurityExpressionRootTests {
given(pe.hasPermission(this.user, targetObject, i)).willReturn(true, false); given(pe.hasPermission(this.user, targetObject, i)).willReturn(true, false);
given(pe.hasPermission(this.user, "x", i)).willReturn(true); given(pe.hasPermission(this.user, "x", i)).willReturn(true);
Expression e = this.parser.parseExpression("hasPermission(this, 2)"); Expression e = this.parser.parseExpression("hasPermission(this, 2)");
assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue(); Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue();
e = this.parser.parseExpression("hasPermission(this, 2)"); e = this.parser.parseExpression("hasPermission(this, 2)");
assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isFalse(); Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isFalse();
e = this.parser.parseExpression("hasPermission(this.x, 2)"); e = this.parser.parseExpression("hasPermission(this.x, 2)");
assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue(); Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue();
} }
} }

Some files were not shown because too many files have changed in this diff Show More