1
0
mirror of synced 2026-07-08 20:30:04 +00:00

Compare commits

...

52 Commits

Author SHA1 Message Date
Marcus Da Coregio e9f3da94fc Release 5.6.2 2022-02-21 10:04:41 -03:00
Marcus Da Coregio 4fb6f936a7 Update spring-ldap-core to 2.3.6.RELEASE
Closes gh-10877
2022-02-21 09:20:57 -03:00
Marcus Da Coregio c431b5479e Update org.springframework.data to 2021.1.2
Closes gh-10876
2022-02-21 09:20:54 -03:00
Marcus Da Coregio fc03950afd Update org.springframework to 5.3.16
Closes gh-10875
2022-02-21 09:20:52 -03:00
Marcus Da Coregio 516b5fed4a Update org.slf4j to 1.7.36
Closes gh-10874
2022-02-21 09:20:49 -03:00
Marcus Da Coregio 165e29e585 Update hibernate-entitymanager to 5.6.5.Final
Closes gh-10873
2022-02-21 09:20:47 -03:00
Marcus Da Coregio 0be3e86b21 Update org.eclipse.jetty to 9.4.45.v20220203
Closes gh-10872
2022-02-21 09:20:44 -03:00
Marcus Da Coregio 96d229a6d1 Update org.aspectj to 1.9.8
Closes gh-10871
2022-02-21 09:20:42 -03:00
Marcus Da Coregio 5098cad9cf Update io.spring.javaformat to 0.0.31
Closes gh-10870
2022-02-21 09:20:40 -03:00
Marcus Da Coregio 883c480af0 Update r2dbc-h2 to 0.8.5.RELEASE
Closes gh-10869
2022-02-21 09:20:37 -03:00
Marcus Da Coregio 0f83e40427 Update io.projectreactor to 2020.0.16
Closes gh-10867
2022-02-21 09:20:32 -03:00
Marcus Da Coregio ad6ef6e4c7 Update mockk to 1.12.2
Closes gh-10866
2022-02-21 09:20:30 -03:00
Marcus Da Coregio 94201e46f1 Update logback-classic to 1.2.10
Closes gh-10865
2022-02-21 09:20:27 -03:00
Rob Winch 910e198cee Add .sdkmanrc 2022-02-18 13:48:26 -06:00
Talerngpong Virojwutikul 45a88fc391 add Kotlin examples for Spring Data Integration of servlet application 2022-02-18 08:49:27 -03:00
giger85 8f01efb9e3 Replace StringUtils class completely
Issue gh-9925
Closes gh-10805
2022-02-10 17:50:07 +01:00
Josh Cummings f53c65b3a0 Polish ignoring() log messaging
- Public API remains unchanged

Issue gh-9334
2022-02-07 15:07:29 -07:00
Manuel Jordan 0be772ff5b Print ignore message DefaultSecurityFilterChain
When either `web.ignoring().mvcMatchers(...)` or
`web.ignoring().antMatchers(...)` methods are used, for all their
variations, the DefaultSecurityFilterChain class now indicates
correctly through its ouput what paths are ignored according the
`ignoring()` settings.

Closes gh-9334
2022-02-07 15:07:29 -07:00
Rob Winch ac990afa5d Document Authorize HTTP Requests for Reactive Security
Closes gh-10801
2022-02-03 13:47:35 -06:00
Ken Dombeck 55cccbf727 Fix broken link to SAML2 login example 2022-02-03 10:20:45 -06:00
Eleftheria Stein a095ea75a2 Fix typo in getting started docs
Closes gh-10736
2022-02-02 10:52:54 +01:00
Marcus Da Coregio a041e7c943 RequestMatcherDelegatingWebInvocationPrivilegeEvaluator doesn't provided access to the ServletContext
Closes gh-10779
2022-01-31 09:50:17 -03:00
Rob Winch 4f3072b3d9 Exclude javax from hibernate dependency
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 13c467734a Remove javax.transaction
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch c01b2b946b Additional removal of javax.inject
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 549f9dab64 dependencies jakarta.transaction-api
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 58090c37ea jsr250-api -> jakarta.annotation-api
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 38344e8de6 javax.servlet.jsp-api -> jakarta.servlet.jsp-api
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 6f319f0b13 javax.servlet.jsp.jstl-api -> jakarta.servlet.jsp.jstl-api
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 4e71b26ea4 Exclude javax from cas-client-core
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 04f3bbcefa javax.xml.bind:jaxb-api -> jakarta.xml.bind:jakarta.xml.bind-api
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch c67ee6f2a8 javax.servlet:javax.servlet-api -> jakarta.servlet:jakarta.servlet-api
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 4b590ce31f Remove javax.inject
Issue gh-10501
2022-01-19 15:32:12 -06:00
Rob Winch 5902b46e9b Remove jcl-over-slf4j
Issue gh-10499

# Conflicts:
#	dependencies/spring-security-dependencies.gradle
2022-01-19 15:32:01 -06:00
Rob Winch 62449d6fa2 Remove commons-logging
Closes gh-10499
2022-01-19 15:31:22 -06:00
Rob Winch aa06088077 Add CheckProhibitedDependenciesLifecyclePlugin
Issue gh-10501
2022-01-19 15:30:34 -06:00
Rob Winch 2a60776c8a Add CheckClasspathForProhibitedDependencies
Issues gh-10499 gh-10501
2022-01-19 15:30:34 -06:00
Rob Winch f411ecada2 CheckAntoraVersionTask has optional properties 2022-01-18 14:29:39 -06:00
Rob Winch 8eeacf4a36 Fix Antora for Milestone & RC
- Verify Antora display_version
- Run workflow for tags
- Allow run workflow manually

Issue gh-10765
2022-01-18 14:20:47 -06:00
Josh Cummings 20c252982e Remove SAML 2.0 Logout Default
Closes gh-10607
2022-01-14 15:29:50 -07:00
Josh Cummings 6c5ac0d8ec Use noNullElements
Collection#contains(null) does not work for all collection types

Closes gh-10703
2022-01-14 15:09:21 -07:00
Marcus Da Coregio e1cb375fbf Make source code compatible with JDK 8
Closes gh-10695
2022-01-12 16:39:50 -03:00
Marcus Da Coregio e7e3f06044 Fix @since tag
Issue gh-10590, gh-10554
2022-01-06 13:22:13 -03:00
Marcus Da Coregio 994e93741b Configure WebInvocationPrivilegeEvaluator bean for multiple filter chains
Closes gh-10554
2022-01-05 14:06:47 -03:00
Marcus Da Coregio 04e1a11e35 Add RequestMatcherEntry 2022-01-05 14:06:47 -03:00
Marcus Da Coregio 547056d5cc Introduce AuthorizationManagerWebInvocationPrivilegeEvaluator
Closes gh-10590
2022-01-05 14:06:47 -03:00
Rob Winch 1fa35fb9a8 Antora prerelease: true for milestone and rc 2022-01-05 10:00:56 -06:00
Rob Winch a49914e631 Update RELEASE.adoc for antora.yml 2022-01-04 15:52:05 -06:00
Rob Winch 783a43ea35 Add CheckAntoraVersionPlugin 2022-01-04 15:52:05 -06:00
Rob Winch 07a176d11e Update antora to 5.6.2 2022-01-04 15:52:05 -06:00
Josh Cummings c664fbc1a3 Support No SingleLogoutServiceLocation
Closes gh-10674
2022-01-03 13:38:47 -07:00
Steve Riesenberg af412c4d65 Next Development Version 2021-12-20 15:24:46 -06:00
80 changed files with 1952 additions and 229 deletions
+2
View File
@@ -1,9 +1,11 @@
name: Generate Antora Files and Request Build
on:
workflow_dispatch:
push:
branches-ignore:
- 'gh-pages'
tags: '**'
env:
GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
+6
View File
@@ -0,0 +1,6 @@
# Use sdkman to run "sdk env" to initialize with correct JDK version
# Enable auto-env through the sdkman_auto_env config
# See https://sdkman.io/usage#config
# A summary is to add the following to ~/.sdkman/etc/config
# sdkman_auto_env=true
java=11.0.14-tem
+11 -1
View File
@@ -78,6 +78,16 @@ Alternatively, you can manually check using https://github.com/spring-projects/s
Update the version number in `gradle.properties` for the release, for example `5.5.0-M1`, `5.5.0-RC1`, `5.5.0`
= Update Antora Version
You will need to update the antora.yml version.
If you are unsure of what the values should be, the following task will instruct you what the expected values are:
[source,bash]
----
./gradlew :spring-security-docs:antoraCheckVersion
----
= Build Locally
Run the build using
@@ -119,7 +129,7 @@ git push origin 5.4.0-RC1
== 7. Update to Next Development Version
* Update `gradle.properties` version to next `+SNAPSHOT+` version and then push
* Update `gradle.properties` version to next `+SNAPSHOT+` version, update antora.yml, and then push
== 8. Update version on project page
+1
View File
@@ -106,6 +106,7 @@ subprojects {
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
options.release = 8
}
}
+11 -2
View File
@@ -27,6 +27,10 @@ sourceSets {
gradlePlugin {
plugins {
checkAntoraVersion {
id = "org.springframework.antora.check-version"
implementationClass = "org.springframework.gradle.antora.CheckAntoraVersionPlugin"
}
trang {
id = "trang"
implementationClass = "trang.TrangPlugin"
@@ -72,10 +76,11 @@ dependencies {
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.thaiopensource:trang:20091111'
implementation 'net.sourceforge.saxon:saxon:9.1.0.8'
implementation 'org.yaml:snakeyaml:1.30'
implementation localGroovy()
implementation 'io.github.gradle-nexus:publish-plugin:1.1.0'
implementation 'io.projectreactor:reactor-core:3.4.13'
implementation 'io.projectreactor:reactor-core:3.4.15'
implementation 'gradle.plugin.org.gretty:gretty:3.0.1'
implementation 'com.apollographql.apollo:apollo-runtime:2.4.5'
implementation 'com.github.ben-manes:gradle-versions-plugin:0.38.0'
@@ -99,7 +104,11 @@ dependencies {
}
test {
tasks.named('test', Test).configure {
onlyIf { !project.hasProperty("buildSrc.skipTests") }
useJUnitPlatform()
jvmArgs(
'--add-opens', 'java.base/java.lang=ALL-UNNAMED',
'--add-opens', 'java.base/java.util=ALL-UNNAMED'
)
}
@@ -21,6 +21,7 @@ import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.plugins.PluginManager
import org.springframework.gradle.classpath.CheckProhibitedDependenciesLifecyclePlugin
import org.springframework.gradle.maven.SpringNexusPublishPlugin
class RootProjectPlugin implements Plugin<Project> {
@@ -32,6 +33,7 @@ class RootProjectPlugin implements Plugin<Project> {
pluginManager.apply(SchemaPlugin)
pluginManager.apply(NoHttpPlugin)
pluginManager.apply(SpringNexusPublishPlugin)
pluginManager.apply(CheckProhibitedDependenciesLifecyclePlugin)
pluginManager.apply("org.sonarqube")
project.repositories.mavenCentral()
@@ -20,6 +20,7 @@ import org.gradle.api.Project
import org.gradle.api.plugins.JavaLibraryPlugin;
import org.gradle.api.plugins.MavenPlugin;
import org.gradle.api.plugins.PluginManager
import org.springframework.gradle.classpath.CheckClasspathForProhibitedDependenciesPlugin;
import org.springframework.gradle.maven.SpringMavenPlugin;
/**
@@ -32,6 +33,7 @@ class SpringModulePlugin extends AbstractSpringJavaPlugin {
PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(JavaLibraryPlugin.class)
pluginManager.apply(SpringMavenPlugin.class);
pluginManager.apply(CheckClasspathForProhibitedDependenciesPlugin.class);
pluginManager.apply("io.spring.convention.jacoco");
def deployArtifacts = project.task("deployArtifacts")
@@ -0,0 +1,85 @@
package org.springframework.gradle.antora;
import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
public class CheckAntoraVersionPlugin implements Plugin<Project> {
public static final String ANTORA_CHECK_VERSION_TASK_NAME = "antoraCheckVersion";
@Override
public void apply(Project project) {
TaskProvider<CheckAntoraVersionTask> antoraCheckVersion = project.getTasks().register(ANTORA_CHECK_VERSION_TASK_NAME, CheckAntoraVersionTask.class, new Action<CheckAntoraVersionTask>() {
@Override
public void execute(CheckAntoraVersionTask antoraCheckVersion) {
antoraCheckVersion.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
antoraCheckVersion.setDescription("Checks the antora.yml version properties match the Gradle version");
antoraCheckVersion.getAntoraVersion().convention(project.provider(() -> getDefaultAntoraVersion(project)));
antoraCheckVersion.getAntoraPrerelease().convention(project.provider(() -> getDefaultAntoraPrerelease(project)));
antoraCheckVersion.getAntoraDisplayVersion().convention(project.provider(() -> getDefaultAntoraDisplayVersion(project)));
antoraCheckVersion.getAntoraYmlFile().fileProvider(project.provider(() -> project.file("antora.yml")));
}
});
project.getPlugins().withType(LifecycleBasePlugin.class, new Action<LifecycleBasePlugin>() {
@Override
public void execute(LifecycleBasePlugin lifecycleBasePlugin) {
project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME).configure(new Action<Task>() {
@Override
public void execute(Task check) {
check.dependsOn(antoraCheckVersion);
}
});
}
});
}
private static String getDefaultAntoraVersion(Project project) {
String projectVersion = getProjectVersion(project);
int preReleaseIndex = getSnapshotIndex(projectVersion);
return isSnapshot(projectVersion) ? projectVersion.substring(0, preReleaseIndex) : projectVersion;
}
private static String getDefaultAntoraPrerelease(Project project) {
String projectVersion = getProjectVersion(project);
if (isSnapshot(projectVersion)) {
int preReleaseIndex = getSnapshotIndex(projectVersion);
return projectVersion.substring(preReleaseIndex);
}
if (isPreRelease(projectVersion)) {
return Boolean.TRUE.toString();
}
return null;
}
private static String getDefaultAntoraDisplayVersion(Project project) {
String projectVersion = getProjectVersion(project);
if (!isSnapshot(projectVersion) && isPreRelease(projectVersion)) {
return getDefaultAntoraVersion(project);
}
return null;
}
private static String getProjectVersion(Project project) {
Object projectVersion = project.getVersion();
if (projectVersion == null) {
throw new GradleException("Please define antoraVersion and antoraPrerelease on " + ANTORA_CHECK_VERSION_TASK_NAME + " or provide a Project version so they can be defaulted");
}
return String.valueOf(projectVersion);
}
private static boolean isSnapshot(String projectVersion) {
return getSnapshotIndex(projectVersion) >= 0;
}
private static int getSnapshotIndex(String projectVersion) {
return projectVersion.lastIndexOf("-SNAPSHOT");
}
private static boolean isPreRelease(String projectVersion) {
return projectVersion.lastIndexOf("-") >= 0;
}
}
@@ -0,0 +1,96 @@
package org.springframework.gradle.antora;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.Optional;
import org.gradle.api.tasks.TaskAction;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.representer.Representer;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public abstract class CheckAntoraVersionTask extends DefaultTask {
@TaskAction
public void check() throws FileNotFoundException {
File antoraYmlFile = getAntoraYmlFile().getAsFile().get();
String expectedAntoraVersion = getAntoraVersion().get();
String expectedAntoraPrerelease = getAntoraPrerelease().getOrElse(null);
String expectedAntoraDisplayVersion = getAntoraDisplayVersion().getOrElse(null);
Representer representer = new Representer();
representer.getPropertyUtils().setSkipMissingProperties(true);
Yaml yaml = new Yaml(new Constructor(AntoraYml.class), representer);
AntoraYml antoraYml = yaml.load(new FileInputStream(antoraYmlFile));
String actualAntoraPrerelease = antoraYml.getPrerelease();
boolean preReleaseMatches = antoraYml.getPrerelease() == null && expectedAntoraPrerelease == null ||
(actualAntoraPrerelease != null && actualAntoraPrerelease.equals(expectedAntoraPrerelease));
String actualAntoraDisplayVersion = antoraYml.getDisplay_version();
boolean displayVersionMatches = antoraYml.getDisplay_version() == null && expectedAntoraDisplayVersion == null ||
(actualAntoraDisplayVersion != null && actualAntoraDisplayVersion.equals(expectedAntoraDisplayVersion));
String actualAntoraVersion = antoraYml.getVersion();
if (!preReleaseMatches ||
!displayVersionMatches ||
!expectedAntoraVersion.equals(actualAntoraVersion)) {
throw new GradleException("The Gradle version of '" + getProject().getVersion() + "' should have version: '"
+ expectedAntoraVersion + "' prerelease: '" + expectedAntoraPrerelease + "' display_version: '"
+ expectedAntoraDisplayVersion + "' defined in " + antoraYmlFile + " but got version: '"
+ actualAntoraVersion + "' prerelease: '" + actualAntoraPrerelease + "' display_version: '" + actualAntoraDisplayVersion + "'");
}
}
@InputFile
public abstract RegularFileProperty getAntoraYmlFile();
@Input
public abstract Property<String> getAntoraVersion();
@Input
@Optional
public abstract Property<String> getAntoraPrerelease();
@Input
@Optional
public abstract Property<String> getAntoraDisplayVersion();
public static class AntoraYml {
private String version;
private String prerelease;
private String display_version;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getPrerelease() {
return prerelease;
}
public void setPrerelease(String prerelease) {
this.prerelease = prerelease;
}
public String getDisplay_version() {
return display_version;
}
public void setDisplay_version(String display_version) {
this.display_version = display_version;
}
}
}
@@ -0,0 +1,99 @@
/*
* Copyright 2012-2022 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.gradle.classpath;
import org.gradle.api.DefaultTask;
import org.gradle.api.GradleException;
import org.gradle.api.Task;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ModuleVersionIdentifier;
import org.gradle.api.artifacts.ResolvedConfiguration;
import org.gradle.api.file.FileCollection;
import org.gradle.api.tasks.Classpath;
import org.gradle.api.tasks.TaskAction;
import java.io.IOException;
import java.util.TreeSet;
import java.util.stream.Collectors;
/**
* A {@link Task} for checking the classpath for prohibited dependencies.
*
* @author Andy Wilkinson
*/
public class CheckClasspathForProhibitedDependencies extends DefaultTask {
private Configuration classpath;
public CheckClasspathForProhibitedDependencies() {
getOutputs().upToDateWhen((task) -> true);
}
public void setClasspath(Configuration classpath) {
this.classpath = classpath;
}
@Classpath
public FileCollection getClasspath() {
return this.classpath;
}
@TaskAction
public void checkForProhibitedDependencies() throws IOException {
ResolvedConfiguration resolvedConfiguration = this.classpath.getResolvedConfiguration();
TreeSet<String> prohibited = resolvedConfiguration.getResolvedArtifacts().stream()
.map((artifact) -> artifact.getModuleVersion().getId()).filter(this::prohibited)
.map((id) -> id.getGroup() + ":" + id.getName()).collect(Collectors.toCollection(TreeSet::new));
if (!prohibited.isEmpty()) {
StringBuilder message = new StringBuilder(String.format("Found prohibited dependencies in '%s':%n", this.classpath.getName()));
for (String dependency : prohibited) {
message.append(String.format(" %s%n", dependency));
}
throw new GradleException(message.toString());
}
}
private boolean prohibited(ModuleVersionIdentifier id) {
String group = id.getGroup();
if (group.equals("javax.batch")) {
return false;
}
if (group.equals("javax.cache")) {
return false;
}
if (group.equals("javax.money")) {
return false;
}
if (group.startsWith("javax")) {
return true;
}
if (group.equals("commons-logging")) {
return true;
}
if (group.equals("org.slf4j") && id.getName().equals("jcl-over-slf4j")) {
return true;
}
if (group.startsWith("org.jboss.spec")) {
return true;
}
if (group.equals("org.apache.geronimo.specs")) {
return true;
}
return false;
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 2012-2022 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.gradle.classpath;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ConfigurationContainer;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.api.tasks.TaskProvider;
import org.gradle.language.base.plugins.LifecycleBasePlugin;
import org.springframework.util.StringUtils;
/**
* @author Andy Wilkinson
* @author Rob Winch
*/
public class CheckClasspathForProhibitedDependenciesPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPlugins().apply(CheckProhibitedDependenciesLifecyclePlugin.class);
project.getPlugins().withType(JavaBasePlugin.class, javaBasePlugin -> {
configureProhibitedDependencyChecks(project);
});
}
private void configureProhibitedDependencyChecks(Project project) {
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
sourceSets.all((sourceSet) -> createProhibitedDependenciesChecks(project,
sourceSet.getCompileClasspathConfigurationName(), sourceSet.getRuntimeClasspathConfigurationName()));
}
private void createProhibitedDependenciesChecks(Project project, String... configurationNames) {
ConfigurationContainer configurations = project.getConfigurations();
for (String configurationName : configurationNames) {
Configuration configuration = configurations.getByName(configurationName);
createProhibitedDependenciesCheck(configuration, project);
}
}
private void createProhibitedDependenciesCheck(Configuration classpath, Project project) {
String taskName = "check" + StringUtils.capitalize(classpath.getName() + "ForProhibitedDependencies");
TaskProvider<CheckClasspathForProhibitedDependencies> checkClasspathTask = project.getTasks().register(taskName,
CheckClasspathForProhibitedDependencies.class, checkClasspath -> {
checkClasspath.setGroup(LifecycleBasePlugin.CHECK_TASK_NAME);
checkClasspath.setDescription("Checks " + classpath.getName() + " for prohibited dependencies");
checkClasspath.setClasspath(classpath);
});
project.getTasks().named(CheckProhibitedDependenciesLifecyclePlugin.CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME, checkProhibitedTask -> checkProhibitedTask.dependsOn(checkClasspathTask));
}
}
@@ -0,0 +1,41 @@
/*
* Copyright 2012-2022 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.gradle.classpath;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.tasks.TaskProvider;
/**
* @author Rob Winch
*/
public class CheckProhibitedDependenciesLifecyclePlugin implements Plugin<Project> {
public static final String CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME = "checkForProhibitedDependencies";
@Override
public void apply(Project project) {
TaskProvider<Task> checkProhibitedDependencies = project.getTasks().register(CheckProhibitedDependenciesLifecyclePlugin.CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME, task -> {
task.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
task.setDescription("Checks both the compile/runtime classpath of every SourceSet for prohibited dependencies");
});
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> {
checkTask.dependsOn(checkProhibitedDependencies);
});
}
}
@@ -0,0 +1,270 @@
package org.springframework.gradle.antora;
import org.apache.commons.io.IOUtils;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.testfixtures.ProjectBuilder;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIOException;
class CheckAntoraVersionPluginTests {
@Test
void defaultsPropertiesWhenSnapshot() {
String expectedVersion = "1.0.0-SNAPSHOT";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0");
assertThat(checkAntoraVersionTask.getAntoraPrerelease().get()).isEqualTo("-SNAPSHOT");
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().isPresent()).isFalse();
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
}
@Test
void defaultsPropertiesWhenMilestone() {
String expectedVersion = "1.0.0-M1";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0-M1");
assertThat(checkAntoraVersionTask.getAntoraPrerelease().get()).isEqualTo("true");
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().get()).isEqualTo(checkAntoraVersionTask.getAntoraVersion().get());
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
}
@Test
void defaultsPropertiesWhenRc() {
String expectedVersion = "1.0.0-RC1";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0-RC1");
assertThat(checkAntoraVersionTask.getAntoraPrerelease().get()).isEqualTo("true");
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().get()).isEqualTo(checkAntoraVersionTask.getAntoraVersion().get());
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
}
@Test
void defaultsPropertiesWhenRelease() {
String expectedVersion = "1.0.0";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0");
assertThat(checkAntoraVersionTask.getAntoraPrerelease().isPresent()).isFalse();
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().isPresent()).isFalse();
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
}
@Test
void explicitProperties() {
Project project = ProjectBuilder.builder().build();
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
checkAntoraVersionTask.getAntoraVersion().set("1.0.0");
checkAntoraVersionTask.getAntoraPrerelease().set("-SNAPSHOT");
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0");
assertThat(checkAntoraVersionTask.getAntoraPrerelease().get()).isEqualTo("-SNAPSHOT");
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().isPresent()).isFalse();
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
}
@Test
void versionNotDefined() throws Exception {
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThatExceptionOfType(GradleException.class).isThrownBy(() -> checkAntoraVersionTask.check());
}
@Test
void antoraFileNotFound() throws Exception {
String expectedVersion = "1.0.0-SNAPSHOT";
Project project = ProjectBuilder.builder().build();
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThatIOException().isThrownBy(() -> checkAntoraVersionTask.check());
}
@Test
void actualAntoraPrereleaseNull() throws Exception {
String expectedVersion = "1.0.0-SNAPSHOT";
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
assertThatExceptionOfType(GradleException.class).isThrownBy(() -> checkAntoraVersionTask.check());
}
@Test
void matchesWhenSnapshot() throws Exception {
String expectedVersion = "1.0.0-SNAPSHOT";
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'\nprerelease: '-SNAPSHOT'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
checkAntoraVersionTask.check();
}
@Test
void matchesWhenMilestone() throws Exception {
String expectedVersion = "1.0.0-M1";
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0-M1'\nprerelease: 'true'\ndisplay_version: '1.0.0-M1'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
checkAntoraVersionTask.check();
}
@Test
void matchesWhenRc() throws Exception {
String expectedVersion = "1.0.0-RC1";
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0-RC1'\nprerelease: 'true'\ndisplay_version: '1.0.0-RC1'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
checkAntoraVersionTask.check();
}
@Test
void matchesWhenReleaseAndPrereleaseUndefined() throws Exception {
String expectedVersion = "1.0.0";
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.setVersion(expectedVersion);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
checkAntoraVersionTask.check();
}
@Test
void matchesWhenExplicitRelease() throws Exception {
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
((CheckAntoraVersionTask) task).getAntoraVersion().set("1.0.0");
checkAntoraVersionTask.check();
}
@Test
void matchesWhenExplicitPrerelease() throws Exception {
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("version: '1.0.0'\nprerelease: '-SNAPSHOT'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
((CheckAntoraVersionTask) task).getAntoraVersion().set("1.0.0");
((CheckAntoraVersionTask) task).getAntoraPrerelease().set("-SNAPSHOT");
checkAntoraVersionTask.check();
}
@Test
void matchesWhenMissingPropertyDefined() throws Exception {
Project project = ProjectBuilder.builder().build();
File rootDir = project.getRootDir();
IOUtils.write("name: 'ROOT'\nversion: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
project.getPluginManager().apply(CheckAntoraVersionPlugin.class);
Task task = project.getTasks().findByName(CheckAntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
((CheckAntoraVersionTask) task).getAntoraVersion().set("1.0.0");
checkAntoraVersionTask.check();
}
}
@@ -9,6 +9,6 @@ repositories {
}
dependencies {
optional 'javax.servlet:javax.servlet-api:3.1.0'
optional 'jakarta.servlet:jakarta.servlet-api:3.1.0'
testCompile 'junit:junit:4.12'
}
+5 -2
View File
@@ -4,7 +4,10 @@ dependencies {
management platform(project(":spring-security-dependencies"))
api project(':spring-security-core')
api project(':spring-security-web')
api 'org.jasig.cas.client:cas-client-core'
api('org.jasig.cas.client:cas-client-core') {
exclude group: 'org.glassfish.jaxb', module: 'jaxb-core'
exclude group: 'javax.xml.bind', module: 'jaxb-api'
}
api 'org.springframework:spring-beans'
api 'org.springframework:spring-context'
api 'org.springframework:spring-core'
@@ -13,7 +16,7 @@ dependencies {
optional 'com.fasterxml.jackson.core:jackson-databind'
optional 'net.sf.ehcache:ehcache'
provided 'javax.servlet:javax.servlet-api'
provided 'jakarta.servlet:jakarta.servlet-api'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
+13 -6
View File
@@ -37,9 +37,9 @@ dependencies {
optional'org.springframework:spring-websocket'
optional 'org.jetbrains.kotlin:kotlin-reflect'
optional 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
optional 'javax.annotation:jsr250-api'
optional 'jakarta.annotation:jakarta.annotation-api'
provided 'javax.servlet:javax.servlet-api'
provided 'jakarta.servlet:jakarta.servlet-api'
testImplementation project(':spring-security-aspects')
testImplementation project(':spring-security-cas')
@@ -62,8 +62,10 @@ dependencies {
testImplementation 'ch.qos.logback:logback-classic'
testImplementation 'io.projectreactor.netty:reactor-netty'
testImplementation 'io.rsocket:rsocket-transport-netty'
testImplementation 'javax.annotation:jsr250-api:1.0'
testImplementation 'javax.xml.bind:jaxb-api'
testImplementation 'jakarta.annotation:jakarta.annotation-api:1.0'
testImplementation "jakarta.inject:jakarta.inject-api"
testImplementation "jakarta.transaction:jakarta.transaction-api"
testImplementation 'jakarta.xml.bind:jakarta.xml.bind-api'
testImplementation 'ldapsdk:ldapsdk:4.1'
testImplementation('net.sourceforge.htmlunit:htmlunit') {
exclude group: 'commons-logging', module: 'commons-logging'
@@ -75,12 +77,18 @@ dependencies {
testImplementation "org.apache.directory.server:apacheds-server-jndi"
testImplementation 'org.apache.directory.shared:shared-ldap'
testImplementation 'org.eclipse.persistence:javax.persistence'
testImplementation 'org.hibernate:hibernate-entitymanager'
testImplementation('org.hibernate:hibernate-entitymanager') {
exclude group: 'javax.activation', module: 'javax.activation-api'
exclude group: 'javax.persistence', module: 'javax.persistence-api'
exclude group: 'javax.xml.bind', module: 'jaxb-api'
exclude group: 'org.jboss.spec.javax.transaction', module: 'jboss-transaction-api_1.2_spec'
}
testImplementation 'org.hsqldb:hsqldb'
testImplementation 'org.mockito:mockito-core'
testImplementation "org.mockito:mockito-inline"
testImplementation ('org.openid4java:openid4java-nodeps') {
exclude group: 'com.google.code.guice', module: 'guice'
exclude group: 'commons-logging', module: 'commons-logging'
}
testImplementation('org.seleniumhq.selenium:htmlunit-driver') {
exclude group: 'commons-logging', module: 'commons-logging'
@@ -89,7 +97,6 @@ dependencies {
exclude group: 'commons-logging', module: 'commons-logging'
exclude group: 'io.netty', module: 'netty'
}
testImplementation 'org.slf4j:jcl-over-slf4j'
testImplementation 'org.springframework.ldap:spring-ldap-core'
testImplementation 'org.springframework:spring-expression'
testImplementation 'org.springframework:spring-jdbc'
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2019 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.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
@@ -33,6 +34,7 @@ import org.springframework.http.HttpMethod;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityBuilder;
@@ -47,9 +49,12 @@ import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.debug.DebugFilter;
import org.springframework.security.web.firewall.HttpFirewall;
@@ -57,7 +62,9 @@ import org.springframework.security.web.firewall.RequestRejectedHandler;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
import org.springframework.util.Assert;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.filter.DelegatingFilterProxy;
/**
@@ -81,7 +88,7 @@ import org.springframework.web.filter.DelegatingFilterProxy;
* @see WebSecurityConfiguration
*/
public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
implements SecurityBuilder<Filter>, ApplicationContextAware {
implements SecurityBuilder<Filter>, ApplicationContextAware, ServletContextAware {
private final Log logger = LogFactory.getLog(getClass());
@@ -108,6 +115,8 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
private Runnable postBuildAction = () -> {
};
private ServletContext servletContext;
/**
* Creates a new instance
* @param objectPostProcessor the {@link ObjectPostProcessor} to use
@@ -252,6 +261,8 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
* {@link WebSecurityConfigurerAdapter}.
* @param securityInterceptor the {@link FilterSecurityInterceptor} to use
* @return the {@link WebSecurity} for further customizations
* @deprecated Use {@link #privilegeEvaluator(WebInvocationPrivilegeEvaluator)}
* instead
*/
public WebSecurity securityInterceptor(FilterSecurityInterceptor securityInterceptor) {
this.filterSecurityInterceptor = securityInterceptor;
@@ -278,11 +289,24 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
+ ".addSecurityFilterChainBuilder directly");
int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries = new ArrayList<>();
for (RequestMatcher ignoredRequest : this.ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
WebSecurity.this.logger.warn("You are asking Spring Security to ignore " + ignoredRequest
+ ". This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.");
SecurityFilterChain securityFilterChain = new DefaultSecurityFilterChain(ignoredRequest);
securityFilterChains.add(securityFilterChain);
requestMatcherPrivilegeEvaluatorsEntries
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
}
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
SecurityFilterChain securityFilterChain = securityFilterChainBuilder.build();
securityFilterChains.add(securityFilterChain);
requestMatcherPrivilegeEvaluatorsEntries
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
}
if (this.privilegeEvaluator == null) {
this.privilegeEvaluator = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
requestMatcherPrivilegeEvaluatorsEntries);
}
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (this.httpFirewall != null) {
@@ -306,6 +330,26 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
return result;
}
private RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> getRequestMatcherPrivilegeEvaluatorsEntry(
SecurityFilterChain securityFilterChain) {
List<WebInvocationPrivilegeEvaluator> privilegeEvaluators = new ArrayList<>();
for (Filter filter : securityFilterChain.getFilters()) {
if (filter instanceof FilterSecurityInterceptor) {
DefaultWebInvocationPrivilegeEvaluator defaultWebInvocationPrivilegeEvaluator = new DefaultWebInvocationPrivilegeEvaluator(
(FilterSecurityInterceptor) filter);
defaultWebInvocationPrivilegeEvaluator.setServletContext(this.servletContext);
privilegeEvaluators.add(defaultWebInvocationPrivilegeEvaluator);
continue;
}
if (filter instanceof AuthorizationFilter) {
AuthorizationManager<HttpServletRequest> authorizationManager = ((AuthorizationFilter) filter)
.getAuthorizationManager();
privilegeEvaluators.add(new AuthorizationManagerWebInvocationPrivilegeEvaluator(authorizationManager));
}
}
return new RequestMatcherEntry<>(securityFilterChain::matches, privilegeEvaluators);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.defaultWebSecurityExpressionHandler.setApplicationContext(applicationContext);
@@ -333,6 +377,11 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
}
}
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
/**
* An {@link IgnoredRequestConfigurer} that allows optionally configuring the
* {@link MvcRequestMatcher#setMethod(HttpMethod)}
@@ -128,8 +128,8 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
}
/**
* Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary for the JSP
* tag support.
* Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary to evaluate
* privileges for a given web URI
* @return the {@link WebInvocationPrivilegeEvaluator}
*/
@Bean
@@ -297,8 +297,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
if (version != null) {
return version;
}
return Version.class.getModule().getDescriptor().version().map(Object::toString)
.orElseThrow(() -> new IllegalStateException("cannot determine OpenSAML version"));
return Version.getVersion();
}
private void registerDefaultAuthenticationProvider(B http) {
@@ -319,8 +319,7 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
if (version != null) {
return version;
}
return Version.class.getModule().getDescriptor().version().map(Object::toString)
.orElseThrow(() -> new IllegalStateException("cannot determine OpenSAML version"));
return Version.getVersion();
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
@@ -62,7 +63,7 @@ import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.test.web.servlet.MockMvc;
@@ -84,6 +85,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Rob Winch
* @author Joe Grandja
* @author Evgeniy Cheban
* @author Marcus Da Coregio
*/
@ExtendWith(SpringTestContextExtension.class)
public class WebSecurityConfigurationTests {
@@ -218,10 +220,10 @@ public class WebSecurityConfigurationTests {
}
@Test
public void loadConfigWhenDefaultWebInvocationPrivilegeEvaluatorThenDefaultIsRegistered() {
public void loadConfigWhenDefaultWebInvocationPrivilegeEvaluatorThenRequestMatcherIsRegistered() {
this.spring.register(WebInvocationPrivilegeEvaluatorDefaultsConfig.class).autowire();
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
.isInstanceOf(DefaultWebInvocationPrivilegeEvaluator.class);
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
}
@Test
@@ -229,7 +231,7 @@ public class WebSecurityConfigurationTests {
this.spring.register(AuthorizeRequestsFilterChainConfig.class).autowire();
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
.isInstanceOf(DefaultWebInvocationPrivilegeEvaluator.class);
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
}
// SEC-2303
@@ -375,6 +377,69 @@ public class WebSecurityConfigurationTests {
assertThat(filterChains.get(1).matches(request)).isTrue();
}
@Test
public void loadConfigWhenTwoSecurityFilterChainsThenRequestMatcherDelegatingWebInvocationPrivilegeEvaluator() {
this.spring.register(TwoSecurityFilterChainConfig.class).autowire();
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
}
@Test
public void loadConfigWhenTwoSecurityFilterChainDebugThenRequestMatcherDelegatingWebInvocationPrivilegeEvaluator() {
this.spring.register(TwoSecurityFilterChainConfig.class).autowire();
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
}
// gh-10554
@Test
public void loadConfigWhenMultipleSecurityFilterChainsThenWebInvocationPrivilegeEvaluatorApplySecurity() {
this.spring.register(MultipleSecurityFilterChainConfig.class).autowire();
WebInvocationPrivilegeEvaluator privilegeEvaluator = this.spring.getContext()
.getBean(WebInvocationPrivilegeEvaluator.class);
assertUserPermissions(privilegeEvaluator);
assertAdminPermissions(privilegeEvaluator);
assertAnotherUserPermission(privilegeEvaluator);
}
// gh-10554
@Test
public void loadConfigWhenMultipleSecurityFilterChainAndIgnoringThenWebInvocationPrivilegeEvaluatorAcceptsNullAuthenticationOnIgnored() {
this.spring.register(MultipleSecurityFilterChainIgnoringConfig.class).autowire();
WebInvocationPrivilegeEvaluator privilegeEvaluator = this.spring.getContext()
.getBean(WebInvocationPrivilegeEvaluator.class);
assertUserPermissions(privilegeEvaluator);
assertAdminPermissions(privilegeEvaluator);
assertAnotherUserPermission(privilegeEvaluator);
// null authentication
assertThat(privilegeEvaluator.isAllowed("/user", null)).isFalse();
assertThat(privilegeEvaluator.isAllowed("/admin", null)).isFalse();
assertThat(privilegeEvaluator.isAllowed("/another", null)).isFalse();
assertThat(privilegeEvaluator.isAllowed("/ignoring1", null)).isTrue();
assertThat(privilegeEvaluator.isAllowed("/ignoring1/child", null)).isTrue();
}
private void assertAnotherUserPermission(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
Authentication anotherUser = new TestingAuthenticationToken("anotherUser", "password", "ROLE_ANOTHER");
assertThat(privilegeEvaluator.isAllowed("/user", anotherUser)).isFalse();
assertThat(privilegeEvaluator.isAllowed("/admin", anotherUser)).isFalse();
assertThat(privilegeEvaluator.isAllowed("/another", anotherUser)).isTrue();
}
private void assertAdminPermissions(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
Authentication admin = new TestingAuthenticationToken("admin", "password", "ROLE_ADMIN");
assertThat(privilegeEvaluator.isAllowed("/user", admin)).isFalse();
assertThat(privilegeEvaluator.isAllowed("/admin", admin)).isTrue();
assertThat(privilegeEvaluator.isAllowed("/another", admin)).isTrue();
}
private void assertUserPermissions(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
Authentication user = new TestingAuthenticationToken("user", "password", "ROLE_USER");
assertThat(privilegeEvaluator.isAllowed("/user", user)).isTrue();
assertThat(privilegeEvaluator.isAllowed("/admin", user)).isFalse();
assertThat(privilegeEvaluator.isAllowed("/another", user)).isTrue();
}
@EnableWebSecurity
@Import(AuthenticationTestConfiguration.class)
static class SortedWebSecurityConfigurerAdaptersConfig {
@@ -1008,4 +1073,125 @@ public class WebSecurityConfigurationTests {
}
@EnableWebSecurity
static class TwoSecurityFilterChainConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain path1(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers((requests) -> requests.antMatchers("/path1/**"))
.authorizeRequests((requests) -> requests.anyRequest().authenticated());
// @formatter:on
return http.build();
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE)
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
return http.build();
}
}
@EnableWebSecurity(debug = true)
static class TwoSecurityFilterChainDebugConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain path1(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers((requests) -> requests.antMatchers("/path1/**"))
.authorizeRequests((requests) -> requests.anyRequest().authenticated());
// @formatter:on
return http.build();
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE)
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
return http.build();
}
}
@EnableWebSecurity
@Import(AuthenticationTestConfiguration.class)
static class MultipleSecurityFilterChainConfig {
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain notAuthorized(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers((requests) -> requests.antMatchers("/user"))
.authorizeRequests((requests) -> requests.anyRequest().hasRole("USER"));
// @formatter:on
return http.build();
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public SecurityFilterChain path1(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers((requests) -> requests.antMatchers("/admin"))
.authorizeRequests((requests) -> requests.anyRequest().hasRole("ADMIN"));
// @formatter:on
return http.build();
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE)
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
return http.build();
}
}
@EnableWebSecurity
@Import(AuthenticationTestConfiguration.class)
static class MultipleSecurityFilterChainIgnoringConfig {
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("/ignoring1/**");
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain notAuthorized(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers((requests) -> requests.antMatchers("/user"))
.authorizeRequests((requests) -> requests.anyRequest().hasRole("USER"));
// @formatter:on
return http.build();
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
public SecurityFilterChain admin(HttpSecurity http) throws Exception {
// @formatter:off
http
.requestMatchers((requests) -> requests.antMatchers("/admin"))
.authorizeRequests((requests) -> requests.anyRequest().hasRole("ADMIN"));
// @formatter:on
return http.build();
}
@Bean
@Order(Ordered.LOWEST_PRECEDENCE)
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
return http.build();
}
}
}
@@ -21,11 +21,13 @@ import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import javax.security.auth.x500.X500Principal;
import javax.servlet.http.HttpServletRequest;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import sun.security.x509.X500Name;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
@@ -240,12 +242,8 @@ public class NamespaceHttpX509Tests {
}
private String extractCommonName(X509Certificate certificate) {
try {
return ((X500Name) certificate.getSubjectDN()).getCommonName();
}
catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
X500Principal principal = certificate.getSubjectX500Principal();
return new X500Name(principal.getName()).getRDNs(BCStyle.CN)[0].getFirst().getValue().toString();
}
}
+1 -2
View File
@@ -13,7 +13,7 @@ dependencies {
optional 'com.fasterxml.jackson.core:jackson-databind'
optional 'io.projectreactor:reactor-core'
optional 'javax.annotation:jsr250-api'
optional 'jakarta.annotation:jakarta.annotation-api'
optional 'net.sf.ehcache:ehcache'
optional 'org.aspectj:aspectjrt'
optional 'org.springframework:spring-jdbc'
@@ -31,7 +31,6 @@ dependencies {
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testImplementation 'org.skyscreamer:jsonassert'
testImplementation 'org.slf4j:jcl-over-slf4j'
testImplementation 'org.springframework:spring-test'
testRuntimeOnly 'org.hsqldb:hsqldb'
@@ -18,6 +18,7 @@ package org.springframework.security.core;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -60,15 +61,24 @@ public class SpringSecurityCoreVersionTests {
@BeforeEach
public void setup() throws Exception {
Field logger = ReflectionUtils.findField(SpringSecurityCoreVersion.class, "logger");
StaticFinalReflectionUtils.setField(logger, this.logger);
setFinalStaticField(SpringSecurityCoreVersion.class, "logger", this.logger);
}
@AfterEach
public void cleanup() throws Exception {
System.clearProperty(getDisableChecksProperty());
Field logger = ReflectionUtils.findField(SpringSecurityCoreVersion.class, "logger");
StaticFinalReflectionUtils.setField(logger, LogFactory.getLog(SpringSecurityCoreVersion.class));
setFinalStaticField(SpringSecurityCoreVersion.class, "logger",
LogFactory.getLog(SpringSecurityCoreVersion.class));
}
private static void setFinalStaticField(Class<?> clazz, String fieldName, Object value)
throws ReflectiveOperationException {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, value);
}
@Test
@@ -1,115 +0,0 @@
/*
* Copyright 2008 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.core;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import sun.misc.Unsafe;
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
/**
* Used for setting static variables even if they are private static final.
*
* The code in this class has been adopted from Powermock's <a href=
* "https://github.com/noushadali/powermock/blob/powermock-1.5.4/reflect/src/main/java/org/powermock/reflect/internal/WhiteboxImpl.java#L326">WhiteboxImpl</a>.
*
* @author Rob Winch
*/
final class StaticFinalReflectionUtils {
/**
* Used to support setting static fields that are final using Java's Unsafe. If the
* field is not static final, use
* {@link org.springframework.test.util.ReflectionTestUtils}.
* @param field the field to set
* @param newValue the new value
*/
static void setField(final Field field, final Object newValue) {
try {
field.setAccessible(true);
int fieldModifiersMask = field.getModifiers();
boolean isFinalModifierPresent = (fieldModifiersMask & Modifier.FINAL) == Modifier.FINAL;
if (isFinalModifierPresent) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
Unsafe unsafe = UnsafeUtils.getUnsafe();
long offset = unsafe.staticFieldOffset(field);
Object base = unsafe.staticFieldBase(field);
setFieldUsingUnsafe(base, field.getType(), offset, newValue, unsafe);
return null;
}
catch (Throwable thrown) {
throw new RuntimeException(thrown);
}
}
});
}
else {
field.set(null, newValue);
}
}
catch (SecurityException ex) {
throw new RuntimeException(ex);
}
catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
}
catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}
}
private static void setFieldUsingUnsafe(Object base, Class type, long offset, Object newValue, Unsafe unsafe) {
if (type == Integer.TYPE) {
unsafe.putInt(base, offset, ((Integer) newValue));
}
else if (type == Short.TYPE) {
unsafe.putShort(base, offset, ((Short) newValue));
}
else if (type == Long.TYPE) {
unsafe.putLong(base, offset, ((Long) newValue));
}
else if (type == Byte.TYPE) {
unsafe.putByte(base, offset, ((Byte) newValue));
}
else if (type == Boolean.TYPE) {
unsafe.putBoolean(base, offset, ((Boolean) newValue));
}
else if (type == Float.TYPE) {
unsafe.putFloat(base, offset, ((Float) newValue));
}
else if (type == Double.TYPE) {
unsafe.putDouble(base, offset, ((Double) newValue));
}
else if (type == Character.TYPE) {
unsafe.putChar(base, offset, ((Character) newValue));
}
else {
unsafe.putObject(base, offset, newValue);
}
}
private StaticFinalReflectionUtils() {
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ apply plugin: 'io.spring.convention.spring-module'
dependencies {
management platform(project(":spring-security-dependencies"))
api project(':spring-security-core')
api 'javax.xml.bind:jaxb-api'
api 'jakarta.xml.bind:jakarta.xml.bind-api'
api 'org.springframework.data:spring-data-commons'
api 'org.springframework:spring-core'
+17 -17
View File
@@ -8,15 +8,15 @@ javaPlatform {
dependencies {
api platform("org.springframework:spring-framework-bom:$springFrameworkVersion")
api platform("io.projectreactor:reactor-bom:2020.0.14")
api platform("io.projectreactor:reactor-bom:2020.0.16")
api platform("io.rsocket:rsocket-bom:1.1.1")
api platform("org.junit:junit-bom:5.8.2")
api platform("org.springframework.data:spring-data-bom:2021.1.0")
api platform("org.springframework.data:spring-data-bom:2021.1.2")
api platform("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion")
api platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2")
api platform("com.fasterxml.jackson:jackson-bom:2.13.1")
constraints {
api "ch.qos.logback:logback-classic:1.2.9"
api "ch.qos.logback:logback-classic:1.2.10"
api "com.google.inject:guice:3.0"
api "com.nimbusds:nimbus-jose-jwt:9.14"
api "com.nimbusds:oauth2-oidc-sdk:9.19"
@@ -25,14 +25,15 @@ dependencies {
api "com.unboundid:unboundid-ldapsdk:4.0.14"
api "commons-codec:commons-codec:1.15"
api "commons-collections:commons-collections:3.2.2"
api "commons-logging:commons-logging:1.2"
api "io.mockk:mockk:1.12.1"
api "io.mockk:mockk:1.12.2"
api "io.projectreactor.tools:blockhound:1.0.6.RELEASE"
api "javax.annotation:jsr250-api:1.0"
api "javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.2"
api "javax.servlet.jsp:javax.servlet.jsp-api:2.3.3"
api "javax.servlet:javax.servlet-api:4.0.1"
api "javax.xml.bind:jaxb-api:2.3.1"
api "jakarta.inject:jakarta.inject-api:1.0.5"
api "jakarta.annotation:jakarta.annotation-api:1.3.5"
api "jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:1.2.7"
api "jakarta.servlet.jsp:jakarta.servlet.jsp-api:2.3.6"
api "jakarta.servlet:jakarta.servlet-api:4.0.4"
api "jakarta.transaction:jakarta.transaction-api:1.3.3"
api "jakarta.xml.bind:jakarta.xml.bind-api:2.3.3"
api "ldapsdk:ldapsdk:4.1"
api "net.sf.ehcache:ehcache:2.10.9.2"
api "net.sourceforge.htmlunit:htmlunit:2.54.0"
@@ -49,11 +50,11 @@ dependencies {
api "org.assertj:assertj-core:3.21.0"
api "org.bouncycastle:bcpkix-jdk15on:1.69"
api "org.bouncycastle:bcprov-jdk15on:1.69"
api "org.eclipse.jetty:jetty-server:9.4.44.v20210927"
api "org.eclipse.jetty:jetty-servlet:9.4.44.v20210927"
api "org.eclipse.jetty:jetty-server:9.4.45.v20220203"
api "org.eclipse.jetty:jetty-servlet:9.4.45.v20220203"
api "org.eclipse.persistence:javax.persistence:2.2.1"
api "org.hamcrest:hamcrest:2.2"
api "org.hibernate:hibernate-entitymanager:5.6.3.Final"
api "org.hibernate:hibernate-entitymanager:5.6.5.Final"
api "org.hsqldb:hsqldb:2.6.1"
api "org.jasig.cas.client:cas-client-core:3.6.4"
api "org.mockito:mockito-core:3.12.4"
@@ -68,10 +69,9 @@ dependencies {
api "org.seleniumhq.selenium:selenium-java:3.141.59"
api "org.seleniumhq.selenium:selenium-support:3.141.59"
api "org.skyscreamer:jsonassert:1.5.0"
api "org.slf4j:jcl-over-slf4j:1.7.32"
api "org.slf4j:log4j-over-slf4j:1.7.32"
api "org.slf4j:slf4j-api:1.7.32"
api "org.springframework.ldap:spring-ldap-core:2.3.5.RELEASE"
api "org.slf4j:log4j-over-slf4j:1.7.36"
api "org.slf4j:slf4j-api:1.7.36"
api "org.springframework.ldap:spring-ldap-core:2.3.6.RELEASE"
api "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
}
}
+1 -2
View File
@@ -1,3 +1,2 @@
name: ROOT
version: '5.6.1'
prerelease: '-SNAPSHOT'
version: '5.6.2'
+1
View File
@@ -124,6 +124,7 @@
*** xref:reactive/authentication/x509.adoc[X.509 Authentication]
*** xref:reactive/authentication/logout.adoc[Logout]
** Authorization
*** xref:reactive/authorization/authorize-http-requests.adoc[Authorize HTTP Requests]
*** xref:reactive/authorization/method.adoc[EnableReactiveMethodSecurity]
** xref:reactive/oauth2/index.adoc[OAuth2]
*** xref:reactive/oauth2/login/index.adoc[OAuth2 Log In]
@@ -53,7 +53,7 @@ If you wish to override the Spring Security version, you may do so by providing
<properties>
<!-- ... -->
<spring-security.version>{spring-security-version}</spring-security.version>
</dependencies>
</properties>
----
====
@@ -68,7 +68,7 @@ You can do so by adding a Maven property, as the following example shows:
<properties>
<!-- ... -->
<spring.version>{spring-core-version}</spring.version>
</dependencies>
</properties>
----
====
@@ -0,0 +1,104 @@
[[servlet-authorization-authorizationfilter]]
= Authorize ServerHttpRequest
Spring Security provides support for authorizing the incoming HTTP requests.
By default, Spring Securitys authorization will require all requests to be authenticated.
The explicit configuration looks like:
.All Requests Require Authenticated User
====
.Java
[source,java,role="primary"]
----
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges -> exchanges
.anyExchange().authenticated()
)
.httpBasic(withDefaults())
.formLogin(withDefaults());
return http.build();
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
formLogin { }
httpBasic { }
}
}
----
====
We can configure Spring Security to have different rules by adding more rules in order of precedence.
.Multiple Authorize Requests Rules
====
.Java
[source,java,role="primary"]
----
import static org.springframework.security.authorization.AuthorityReactiveAuthorizationManager.hasRole;
// ...
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) {
// @formatter:off
http
// ...
.authorizeExchange((authorize) -> authorize // <1>
.pathMatchers("/resources/**", "/signup", "/about").permitAll() // <2>
.pathMatchers("/admin/**").hasRole("ADMIN") // <3>
.pathMatchers("/db/**").access((authentication, context) -> // <4>
hasRole("ADMIN").check(authentication, context)
.filter(decision -> !decision.isGranted())
.switchIfEmpty(hasRole("DBA").check(authentication, context))
)
.anyExchange().denyAll() // <5>
);
// @formatter:on
return http.build();
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange { // <1>
authorize(pathMatchers("/resources/**", "/signup", "/about"), permitAll) // <2>
authorize("/admin/**", hasRole("ADMIN")) // <3>
authorize("/db/**", { authentication, context -> // <4>
hasRole("ADMIN").check(authentication, context)
.filter({ decision -> !decision.isGranted() })
.switchIfEmpty(hasRole("DBA").check(authentication, context))
})
authorize(anyExchange, denyAll) // <5>
}
// ...
}
}
----
====
<1> There are multiple authorization rules specified.
Each rule is considered in the order they were declared.
<2> We specified multiple URL patterns that any user can access.
Specifically, any user can access a request if the URL starts with "/resources/", equals "/signup", or equals "/about".
<3> Any URL that starts with "/admin/" will be restricted to users who have the authority "ROLE_ADMIN".
You will notice that since we are invoking the `hasRole` method we do not need to specify the "ROLE_" prefix.
<4> Any URL that starts with "/db/" requires the user to have both "ROLE_ADMIN" and "ROLE_DBA".
This demonstrates the flexibility of providing a custom `ReactiveAuthorizationManager` allowing us to implement arbitrary authorization logic.
For simplicity, the sample uses a lambda and delegate to the existing `AuthorityReactiveAuthorizationManager.hasRole` implementation.
However, in a real world situation applications would likely implement the logic in a proper class implementing `ReactiveAuthorizationManager`.
<5> Any URL that has not already been matched on is denied access.
This is a good strategy if you do not want to accidentally forget to update your authorization rules.
@@ -7,10 +7,11 @@ It is not only useful but necessary to include the user in the queries to suppor
[[data-configuration]]
== Spring Data & Spring Security Configuration
To use this support, add `org.springframework.security:spring-security-data` dependency and provide a bean of type `SecurityEvaluationContextExtension`.
In Java Configuration, this would look like:
To use this support, add `org.springframework.security:spring-security-data` dependency and provide a bean of type `SecurityEvaluationContextExtension`:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
@@ -18,6 +19,16 @@ public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Bean
fun securityEvaluationContextExtension(): SecurityEvaluationContextExtension {
return SecurityEvaluationContextExtension()
}
----
====
In XML Configuration, this would look like:
[source,xml]
@@ -31,7 +42,9 @@ In XML Configuration, this would look like:
Now Spring Security can be used within your queries.
For example:
[source,java]
====
.Java
[source,java,role="primary"]
----
@Repository
public interface MessageRepository extends PagingAndSortingRepository<Message,Long> {
@@ -40,6 +53,17 @@ public interface MessageRepository extends PagingAndSortingRepository<Message,Lo
}
----
.Kotlin
[source,kotlin,role="secondary"]
----
@Repository
interface MessageRepository : PagingAndSortingRepository<Message,Long> {
@Query("select m from Message m where m.to.id = ?#{ principal?.id }")
fun findInbox(pageable: Pageable): Page<Message>
}
----
====
This checks to see if the `Authentication.getPrincipal().getId()` is equal to the recipient of the `Message`.
Note that this example assumes you have customized the principal to be an Object that has an id property.
By exposing the `SecurityEvaluationContextExtension` bean, all of the xref:servlet/authorization/expression-based.adoc#common-expressions[Common Security Expressions] are available within the Query.
@@ -14,5 +14,5 @@ This process is similar to the one started in 2017 for xref:servlet/oauth2/index
[NOTE]
====
A working sample for {gh-samples-url}/servlet/spring-boot/java/saml2-login[SAML 2.0 Login] is available in the {gh-samples-url}[Spring Security Samples repository].
A working sample for {gh-samples-url}/servlet/spring-boot/java/saml2/login[SAML 2.0 Login] is available in the {gh-samples-url}[Spring Security Samples repository].
====
@@ -35,6 +35,7 @@ RelyingPartyRegistrationRepository registrations() {
RelyingPartyRegistration registration = RelyingPartyRegistrations
.fromMetadataLocation("https://ap.example.org/metadata")
.registrationId("id")
.singleLogoutServiceLocation("{baseUrl}/logout/saml2/slo")
.signingX509Credentials((signing) -> signing.add(credential)) <1>
.build();
return new InMemoryRelyingPartyRegistrationRepository(registration);
@@ -73,6 +74,10 @@ Also, your application can participate in an AP-initiated logout when the assert
3. Create, sign, and serialize a `<saml2:LogoutResponse>` based on the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`] associated with the just logged-out user
4. Send a redirect or post to the asserting party based on the xref:servlet/saml2/login/overview.adoc#servlet-saml2login-relyingpartyregistration[`RelyingPartyRegistration`]
NOTE: Adding `saml2Logout` adds the capability for logout to the service provider.
Because it is an optional capability, you need to enable it for each individual `RelyingPartyRegistration`.
You can do this by setting the `RelyingPartyRegistration.Builder#singleLogoutServiceLocation` property.
== Configuring Logout Endpoints
There are three behaviors that can be triggered by different endpoints:
+1
View File
@@ -1,5 +1,6 @@
plugins {
id "io.github.rwinch.antora" version "0.0.2"
id "org.springframework.antora.check-version"
}
apply plugin: 'io.spring.convention.docs'
+4 -4
View File
@@ -1,9 +1,9 @@
aspectjVersion=1.9.7
springJavaformatVersion=0.0.29
aspectjVersion=1.9.8
springJavaformatVersion=0.0.31
springBootVersion=2.4.2
springFrameworkVersion=5.3.14
springFrameworkVersion=5.3.16
openSamlVersion=3.4.6
version=5.6.1
version=5.6.2
kotlinVersion=1.5.32
samplesBranch=5.6.x
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
@@ -10,7 +10,7 @@ dependencies {
implementation 'org.springframework:spring-tx'
testImplementation project(':spring-security-web')
testImplementation 'javax.servlet:javax.servlet-api'
testImplementation 'jakarta.servlet:jakarta.servlet-api'
testImplementation 'org.springframework:spring-web'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
+2 -2
View File
@@ -5,7 +5,7 @@ dependencies {
implementation 'org.springframework:spring-context'
implementation 'org.springframework:spring-web'
compileOnly 'javax.servlet:javax.servlet-api'
compileOnly 'jakarta.servlet:jakarta.servlet-api'
testImplementation project(':spring-security-core')
testImplementation project(':spring-security-test')
@@ -21,7 +21,7 @@ dependencies {
testImplementation "org.mockito:mockito-core"
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testImplementation 'javax.servlet:javax.servlet-api'
testImplementation 'jakarta.servlet:jakarta.servlet-api'
testRuntimeOnly project(':spring-security-config')
testRuntimeOnly project(':spring-security-ldap')
-1
View File
@@ -25,7 +25,6 @@ dependencies {
}
testImplementation project(':spring-security-test')
testImplementation 'org.slf4j:jcl-over-slf4j'
testImplementation 'org.slf4j:slf4j-api'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
+1 -2
View File
@@ -12,7 +12,7 @@ dependencies {
optional project(':spring-security-web')
optional 'org.springframework:spring-websocket'
optional 'io.projectreactor:reactor-core'
optional 'javax.servlet:javax.servlet-api'
optional 'jakarta.servlet:jakarta.servlet-api'
testImplementation project(path: ':spring-security-core', configuration: 'tests')
testImplementation 'commons-codec:commons-codec'
@@ -24,7 +24,6 @@ dependencies {
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testImplementation "org.slf4j:slf4j-api"
testImplementation "org.slf4j:jcl-over-slf4j"
testImplementation "org.slf4j:log4j-over-slf4j"
testImplementation "ch.qos.logback:logback-classic"
@@ -23,7 +23,7 @@ dependencies {
testImplementation 'io.projectreactor:reactor-test'
testImplementation 'io.projectreactor.tools:blockhound'
testImplementation 'org.skyscreamer:jsonassert'
testImplementation 'io.r2dbc:r2dbc-h2:0.8.4.RELEASE'
testImplementation 'io.r2dbc:r2dbc-h2:0.8.5.RELEASE'
testImplementation 'io.r2dbc:r2dbc-spi-test:0.8.6.RELEASE'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
@@ -35,5 +35,5 @@ dependencies {
testRuntimeOnly 'org.hsqldb:hsqldb'
provided 'javax.servlet:javax.servlet-api'
provided 'jakarta.servlet:jakarta.servlet-api'
}
@@ -12,7 +12,7 @@ dependencies {
optional 'io.projectreactor:reactor-core'
optional 'org.springframework:spring-webflux'
provided 'javax.servlet:javax.servlet-api'
provided 'jakarta.servlet:jakarta.servlet-api'
testImplementation project(path: ':spring-security-oauth2-jose', configuration: 'tests')
testImplementation 'com.squareup.okhttp3:mockwebserver'
+7 -2
View File
@@ -10,12 +10,14 @@ dependencies {
api project(':spring-security-web')
api('com.google.inject:guice') {
exclude group: 'aopalliance', module: 'aopalliance'
exclude group: 'javax.inject', module: 'javax.inject'
}
// openid4java has a compile time dep on guice with a group
// name which is different from the maven central one.
// We use the maven central version here instead.
api('org.openid4java:openid4java-nodeps') {
exclude group: 'com.google.code.guice', module: 'guice'
exclude group: 'commons-logging', module: 'commons-logging'
}
api 'org.springframework:spring-aop'
api 'org.springframework:spring-beans'
@@ -23,11 +25,14 @@ dependencies {
api 'org.springframework:spring-core'
api 'org.springframework:spring-web'
provided 'javax.servlet:javax.servlet-api'
provided 'jakarta.servlet:jakarta.servlet-api'
runtimeOnly 'net.sourceforge.nekohtml:nekohtml'
runtimeOnly 'org.apache.httpcomponents:httpclient'
runtimeOnly('org.apache.httpcomponents:httpclient') {
exclude group: 'commons-logging', module: 'commons-logging'
}
testImplementation "jakarta.inject:jakarta.inject-api"
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-params"
@@ -50,7 +50,7 @@ dependencies {
opensaml4MainImplementation "org.opensaml:opensaml-saml-api:4.1.0"
opensaml4MainImplementation "org.opensaml:opensaml-saml-impl:4.1.0"
provided 'javax.servlet:javax.servlet-api'
provided 'jakarta.servlet:jakarta.servlet-api'
testImplementation 'com.squareup.okhttp3:mockwebserver'
testImplementation "org.assertj:assertj-core"
@@ -86,7 +86,9 @@ public final class OpenSamlMetadataResolver implements Saml2MetadataResolver {
spSsoDescriptor.getKeyDescriptors()
.addAll(buildKeys(registration.getDecryptionX509Credentials(), UsageType.ENCRYPTION));
spSsoDescriptor.getAssertionConsumerServices().add(buildAssertionConsumerService(registration));
spSsoDescriptor.getSingleLogoutServices().add(buildSingleLogoutService(registration));
if (registration.getSingleLogoutServiceLocation() != null) {
spSsoDescriptor.getSingleLogoutServices().add(buildSingleLogoutService(registration));
}
return spSsoDescriptor;
}
@@ -106,6 +106,8 @@ public final class RelyingPartyRegistration {
Assert.hasText(entityId, "entityId cannot be empty");
Assert.hasText(assertionConsumerServiceLocation, "assertionConsumerServiceLocation cannot be empty");
Assert.notNull(assertionConsumerServiceBinding, "assertionConsumerServiceBinding cannot be null");
Assert.isTrue(singleLogoutServiceLocation == null || singleLogoutServiceBinding != null,
"singleLogoutServiceBinding cannot be null when singleLogoutServiceLocation is set");
Assert.notNull(providerDetails, "providerDetails cannot be null");
Assert.notEmpty(credentials, "credentials cannot be empty");
for (org.springframework.security.saml2.credentials.Saml2X509Credential c : credentials) {
@@ -1012,7 +1014,7 @@ public final class RelyingPartyRegistration {
private Saml2MessageBinding assertionConsumerServiceBinding = Saml2MessageBinding.POST;
private String singleLogoutServiceLocation = "{baseUrl}/logout/saml2/slo";
private String singleLogoutServiceLocation;
private String singleLogoutServiceResponseLocation;
@@ -111,6 +111,9 @@ final class OpenSamlLogoutRequestResolver {
if (registration == null) {
return null;
}
if (registration.getAssertingPartyDetails().getSingleLogoutServiceLocation() == null) {
return null;
}
LogoutRequest logoutRequest = this.logoutRequestBuilder.buildObject();
logoutRequest.setDestination(registration.getAssertingPartyDetails().getSingleLogoutServiceLocation());
Issuer issuer = this.issuerBuilder.buildObject();
@@ -132,6 +132,9 @@ final class OpenSamlLogoutResponseResolver {
if (registration == null) {
return null;
}
if (registration.getAssertingPartyDetails().getSingleLogoutServiceResponseLocation() == null) {
return null;
}
String serialized = request.getParameter(Saml2ParameterNames.SAML_REQUEST);
byte[] b = Saml2Utils.samlDecode(serialized);
LogoutRequest logoutRequest = parse(inflateIfRequired(registration, b));
@@ -120,6 +120,12 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
if (registration.getSingleLogoutServiceLocation() == null) {
this.logger.trace(
"Did not process logout request since RelyingPartyRegistration has not been configured with a logout request endpoint");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
if (!isCorrectBinding(request, registration)) {
this.logger.trace("Did not process logout request since used incorrect binding");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
@@ -120,6 +120,12 @@ public final class Saml2LogoutResponseFilter extends OncePerRequestFilter {
response.sendError(HttpServletResponse.SC_BAD_REQUEST, error.toString());
return;
}
if (registration.getSingleLogoutServiceResponseLocation() == null) {
this.logger.trace(
"Did not process logout response since RelyingPartyRegistration has not been configured with a logout response endpoint");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
return;
}
if (!isCorrectBinding(request, registration)) {
this.logger.trace("Did not process logout request since used incorrect binding");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
@@ -47,7 +47,9 @@ public class OpenSaml3LogoutRequestResolverTests {
this.relyingPartyRegistrationResolver);
logoutRequestResolver.setParametersConsumer((parameters) -> parameters.getLogoutRequest().setID("myid"));
HttpServletRequest request = new MockHttpServletRequest();
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build();
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
.assertingPartyDetails((party) -> party.singleLogoutServiceLocation("https://ap.example.com/logout"))
.build();
Authentication authentication = new TestingAuthenticationToken("user", "password");
given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration);
Saml2LogoutRequest logoutRequest = logoutRequestResolver.resolve(request, authentication);
@@ -53,7 +53,10 @@ public class OpenSaml3LogoutResponseResolverTests {
Consumer<LogoutResponseParameters> parametersConsumer = mock(Consumer.class);
logoutResponseResolver.setParametersConsumer(parametersConsumer);
MockHttpServletRequest request = new MockHttpServletRequest();
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build();
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
.assertingPartyDetails(
(party) -> party.singleLogoutServiceResponseLocation("https://ap.example.com/logout"))
.build();
Authentication authentication = new TestingAuthenticationToken("user", "password");
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
request.setParameter(Saml2ParameterNames.SAML_REQUEST,
@@ -47,7 +47,9 @@ public class OpenSaml4LogoutRequestResolverTests {
this.relyingPartyRegistrationResolver);
logoutRequestResolver.setParametersConsumer((parameters) -> parameters.getLogoutRequest().setID("myid"));
HttpServletRequest request = new MockHttpServletRequest();
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build();
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
.assertingPartyDetails((party) -> party.singleLogoutServiceLocation("https://ap.example.com/logout"))
.build();
Authentication authentication = new TestingAuthenticationToken("user", "password");
given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration);
Saml2LogoutRequest logoutRequest = logoutRequestResolver.resolve(request, authentication);
@@ -53,7 +53,10 @@ public class OpenSaml4LogoutResponseResolverTests {
Consumer<LogoutResponseParameters> parametersConsumer = mock(Consumer.class);
logoutResponseResolver.setParametersConsumer(parametersConsumer);
MockHttpServletRequest request = new MockHttpServletRequest();
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build();
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration()
.assertingPartyDetails(
(party) -> party.singleLogoutServiceResponseLocation("https://ap.example.com/logout"))
.build();
Authentication authentication = new TestingAuthenticationToken("user", "password");
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
request.setParameter(Saml2ParameterNames.SAML_REQUEST,
@@ -61,4 +61,13 @@ public class OpenSamlMetadataResolverTests {
.contains("ResponseLocation=\"https://rp.example.org/logout/saml2/response\"");
}
@Test
public void resolveWhenRelyingPartyNoLogoutThenMetadataMatches() {
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.full()
.singleLogoutServiceLocation(null).build();
OpenSamlMetadataResolver openSamlMetadataResolver = new OpenSamlMetadataResolver();
String metadata = openSamlMetadataResolver.resolve(relyingPartyRegistration);
assertThat(metadata).doesNotContain("ResponseLocation");
}
}
@@ -153,4 +153,20 @@ public class Saml2LogoutRequestFilterTests {
verifyNoInteractions(this.logoutHandler);
}
@Test
public void doFilterWhenNoRelyingPartyLogoutThen401() throws Exception {
Authentication authentication = new TestingAuthenticationToken("user", "password");
SecurityContextHolder.getContext().setAuthentication(authentication);
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/logout/saml2/slo");
request.setServletPath("/logout/saml2/slo");
request.setParameter(Saml2ParameterNames.SAML_REQUEST, "request");
MockHttpServletResponse response = new MockHttpServletResponse();
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full().singleLogoutServiceLocation(null)
.build();
given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration);
this.logoutRequestProcessingFilter.doFilterInternal(request, response, new MockFilterChain());
assertThat(response.getStatus()).isEqualTo(401);
verifyNoInteractions(this.logoutHandler);
}
}
@@ -37,6 +37,7 @@ import org.springframework.security.saml2.provider.service.registration.TestRely
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.mock;
@@ -151,4 +152,23 @@ public class Saml2LogoutResponseFilterTests {
verifyNoInteractions(this.logoutSuccessHandler);
}
@Test
public void doFilterWhenNoRelyingPartyLogoutThen401() throws Exception {
Authentication authentication = new TestingAuthenticationToken("user", "password");
SecurityContextHolder.getContext().setAuthentication(authentication);
MockHttpServletRequest request = new MockHttpServletRequest("POST", "/logout/saml2/slo");
request.setServletPath("/logout/saml2/slo");
request.setParameter(Saml2ParameterNames.SAML_RESPONSE, "response");
MockHttpServletResponse response = new MockHttpServletResponse();
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.full().singleLogoutServiceLocation(null)
.singleLogoutServiceResponseLocation(null).build();
given(this.relyingPartyRegistrationResolver.resolve(any(), any())).willReturn(registration);
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
.samlRequest("request").build();
given(this.logoutRequestRepository.removeLogoutRequest(request, response)).willReturn(logoutRequest);
this.logoutResponseProcessingFilter.doFilterInternal(request, response, new MockFilterChain());
assertThat(response.getStatus()).isEqualTo(401);
verifyNoInteractions(this.logoutSuccessHandler);
}
}
+3 -3
View File
@@ -12,10 +12,10 @@ dependencies {
api 'org.springframework:spring-expression'
api 'org.springframework:spring-web'
provided 'javax.servlet.jsp:javax.servlet.jsp-api'
provided 'javax.servlet:javax.servlet-api'
provided 'jakarta.servlet.jsp:jakarta.servlet.jsp-api'
provided 'jakarta.servlet:jakarta.servlet-api'
testRuntimeOnly 'javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api'
testRuntimeOnly 'jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
+2 -2
View File
@@ -15,13 +15,13 @@ dependencies {
optional 'org.springframework:spring-webmvc'
optional 'org.springframework:spring-webflux'
provided 'javax.servlet:javax.servlet-api'
provided 'jakarta.servlet:jakarta.servlet-api'
testImplementation project(path : ':spring-security-config', configuration : 'tests')
testImplementation 'com.fasterxml.jackson.core:jackson-databind'
testImplementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
testImplementation 'io.projectreactor:reactor-test'
testImplementation 'javax.xml.bind:jaxb-api'
testImplementation 'jakarta.xml.bind:jakarta.xml.bind-api'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-params"
@@ -41,8 +41,6 @@ import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.nimbusds.oauth2.sdk.util.StringUtils;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
@@ -102,6 +100,7 @@ import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
@@ -1206,7 +1205,7 @@ public final class SecurityMockMvcRequestPostProcessors {
return getAuthorities((Collection) scope);
}
String scopes = scope.toString();
if (StringUtils.isBlank(scopes)) {
if (!StringUtils.hasText(scopes)) {
return Collections.emptyList();
}
return getAuthorities(Arrays.asList(scopes.split(" ")));
+2 -2
View File
@@ -17,12 +17,12 @@ dependencies {
optional 'org.springframework:spring-webflux'
optional 'org.springframework:spring-webmvc'
provided 'javax.servlet:javax.servlet-api'
provided 'jakarta.servlet:jakarta.servlet-api'
testImplementation project(path: ':spring-security-core', configuration: 'tests')
testImplementation 'commons-codec:commons-codec'
testImplementation 'io.projectreactor:reactor-test'
testImplementation 'javax.xml.bind:jaxb-api'
testImplementation 'jakarta.xml.bind:jakarta.xml.bind-api'
testImplementation 'org.hamcrest:hamcrest'
testImplementation 'org.mockito:mockito-core'
testImplementation 'org.mockito:mockito-inline'
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2021 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.
@@ -48,7 +48,12 @@ public final class DefaultSecurityFilterChain implements SecurityFilterChain {
}
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
logger.info(LogMessage.format("Will secure %s with %s", requestMatcher, filters));
if (!filters.isEmpty()) {
logger.info(LogMessage.format("Will not secure %s", requestMatcher));
}
else {
logger.info(LogMessage.format("Will secure %s with %s", requestMatcher, filters));
}
this.requestMatcher = requestMatcher;
this.filters = new ArrayList<>(filters);
}
@@ -0,0 +1,57 @@
/*
* Copyright 2002-2021 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.web.access;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
import org.springframework.util.Assert;
/**
* An implementation of {@link WebInvocationPrivilegeEvaluator} which delegates the checks
* to an instance of {@link AuthorizationManager}
*
* @author Marcus Da Coregio
* @since 5.5.5
*/
public final class AuthorizationManagerWebInvocationPrivilegeEvaluator implements WebInvocationPrivilegeEvaluator {
private final AuthorizationManager<HttpServletRequest> authorizationManager;
public AuthorizationManagerWebInvocationPrivilegeEvaluator(
AuthorizationManager<HttpServletRequest> authorizationManager) {
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
this.authorizationManager = authorizationManager;
}
@Override
public boolean isAllowed(String uri, Authentication authentication) {
return isAllowed(null, uri, null, authentication);
}
@Override
public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) {
FilterInvocation filterInvocation = new FilterInvocation(contextPath, uri, method);
AuthorizationDecision decision = this.authorizationManager.check(() -> authentication,
filterInvocation.getHttpRequest());
return decision != null && decision.isGranted();
}
}
@@ -0,0 +1,132 @@
/*
* Copyright 2002-2022 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.web.access;
import java.util.Collections;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
import org.springframework.util.Assert;
import org.springframework.web.context.ServletContextAware;
/**
* A {@link WebInvocationPrivilegeEvaluator} which delegates to a list of
* {@link WebInvocationPrivilegeEvaluator} based on a
* {@link org.springframework.security.web.util.matcher.RequestMatcher} evaluation
*
* @author Marcus Da Coregio
* @since 5.5.5
*/
public final class RequestMatcherDelegatingWebInvocationPrivilegeEvaluator
implements WebInvocationPrivilegeEvaluator, ServletContextAware {
private final List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> delegates;
private ServletContext servletContext;
public RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries) {
Assert.notNull(requestMatcherPrivilegeEvaluatorsEntries, "requestMatcherPrivilegeEvaluators cannot be null");
for (RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> entry : requestMatcherPrivilegeEvaluatorsEntries) {
Assert.notNull(entry.getRequestMatcher(), "requestMatcher cannot be null");
Assert.notNull(entry.getEntry(), "webInvocationPrivilegeEvaluators cannot be null");
}
this.delegates = requestMatcherPrivilegeEvaluatorsEntries;
}
/**
* Determines whether the user represented by the supplied <tt>Authentication</tt>
* object is allowed to invoke the supplied URI.
* <p>
* Uses the provided URI in the
* {@link org.springframework.security.web.util.matcher.RequestMatcher#matches(HttpServletRequest)}
* for every {@code RequestMatcher} configured. If no {@code RequestMatcher} is
* matched, or if there is not an available {@code WebInvocationPrivilegeEvaluator},
* returns {@code true}.
* @param uri the URI excluding the context path (a default context path setting will
* be used)
* @return true if access is allowed, false if denied
*/
@Override
public boolean isAllowed(String uri, Authentication authentication) {
List<WebInvocationPrivilegeEvaluator> privilegeEvaluators = getDelegate(null, uri, null);
if (privilegeEvaluators.isEmpty()) {
return true;
}
for (WebInvocationPrivilegeEvaluator evaluator : privilegeEvaluators) {
boolean isAllowed = evaluator.isAllowed(uri, authentication);
if (!isAllowed) {
return false;
}
}
return true;
}
/**
* Determines whether the user represented by the supplied <tt>Authentication</tt>
* object is allowed to invoke the supplied URI.
* <p>
* Uses the provided URI in the
* {@link org.springframework.security.web.util.matcher.RequestMatcher#matches(HttpServletRequest)}
* for every {@code RequestMatcher} configured. If no {@code RequestMatcher} is
* matched, or if there is not an available {@code WebInvocationPrivilegeEvaluator},
* returns {@code true}.
* @param uri the URI excluding the context path (a default context path setting will
* be used)
* @param contextPath the context path (may be null, in which case a default value
* will be used).
* @param method the HTTP method (or null, for any method)
* @param authentication the <tt>Authentication</tt> instance whose authorities should
* be used in evaluation whether access should be granted.
* @return true if access is allowed, false if denied
*/
@Override
public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) {
List<WebInvocationPrivilegeEvaluator> privilegeEvaluators = getDelegate(contextPath, uri, method);
if (privilegeEvaluators.isEmpty()) {
return true;
}
for (WebInvocationPrivilegeEvaluator evaluator : privilegeEvaluators) {
boolean isAllowed = evaluator.isAllowed(contextPath, uri, method, authentication);
if (!isAllowed) {
return false;
}
}
return true;
}
private List<WebInvocationPrivilegeEvaluator> getDelegate(String contextPath, String uri, String method) {
FilterInvocation filterInvocation = new FilterInvocation(contextPath, uri, method, this.servletContext);
for (RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> delegate : this.delegates) {
if (delegate.getRequestMatcher().matches(filterInvocation.getHttpRequest())) {
return delegate.getEntry();
}
}
return Collections.emptyList();
}
@Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -67,4 +67,12 @@ public class AuthorizationFilter extends OncePerRequestFilter {
return authentication;
}
/**
* Gets the {@link AuthorizationManager} used by this filter
* @return the {@link AuthorizationManager}
*/
public AuthorizationManager<HttpServletRequest> getAuthorizationManager() {
return this.authorizationManager;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2021 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.
@@ -151,6 +151,10 @@ public final class DebugFilter implements Filter {
public void destroy() {
}
public FilterChainProxy getFilterChainProxy() {
return this.filterChainProxy;
}
static class DebugRequestWrapper extends HttpServletRequestWrapper {
private static final Logger logger = new Logger();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2022 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.
@@ -66,7 +66,7 @@ public class MediaTypeServerWebExchangeMatcher implements ServerWebExchangeMatch
*/
public MediaTypeServerWebExchangeMatcher(Collection<MediaType> matchingMediaTypes) {
Assert.notEmpty(matchingMediaTypes, "matchingMediaTypes cannot be null");
Assert.isTrue(!matchingMediaTypes.contains(null),
Assert.noNullElements(matchingMediaTypes,
() -> "matchingMediaTypes cannot contain null. Got " + matchingMediaTypes);
this.matchingMediaTypes = matchingMediaTypes;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2022 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.
@@ -45,7 +45,7 @@ public final class AndRequestMatcher implements RequestMatcher {
*/
public AndRequestMatcher(List<RequestMatcher> requestMatchers) {
Assert.notEmpty(requestMatchers, "requestMatchers must contain a value");
Assert.isTrue(!requestMatchers.contains(null), "requestMatchers cannot contain null values");
Assert.noNullElements(requestMatchers, "requestMatchers cannot contain null values");
this.requestMatchers = requestMatchers;
}
@@ -49,6 +49,7 @@ import org.springframework.web.util.UrlPathHelper;
* @author Rob Winch
* @author Eddú Meléndez
* @author Evgeniy Cheban
* @author Manuel Jordan
* @since 3.1
* @see org.springframework.util.AntPathMatcher
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2022 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.
@@ -40,7 +40,7 @@ public final class OrRequestMatcher implements RequestMatcher {
*/
public OrRequestMatcher(List<RequestMatcher> requestMatchers) {
Assert.notEmpty(requestMatchers, "requestMatchers must contain a value");
Assert.isTrue(!requestMatchers.contains(null), "requestMatchers cannot contain null values");
Assert.noNullElements(requestMatchers, "requestMatchers cannot contain null values");
this.requestMatchers = requestMatchers;
}
@@ -0,0 +1,44 @@
/*
* Copyright 2002-2021 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.web.util.matcher;
/**
* A rich object for associating a {@link RequestMatcher} to another object.
*
* @author Marcus Da Coregio
* @since 5.5.5
*/
public class RequestMatcherEntry<T> {
private final RequestMatcher requestMatcher;
private final T entry;
public RequestMatcherEntry(RequestMatcher requestMatcher, T entry) {
this.requestMatcher = requestMatcher;
this.entry = entry;
}
public RequestMatcher getRequestMatcher() {
return this.requestMatcher;
}
public T getEntry() {
return this.entry;
}
}
@@ -0,0 +1,68 @@
/*
* Copyright 2002-2021 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.web.access;
import javax.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class)
class AuthorizationManagerWebInvocationPrivilegeEvaluatorTests {
@InjectMocks
private AuthorizationManagerWebInvocationPrivilegeEvaluator privilegeEvaluator;
@Mock
private AuthorizationManager<HttpServletRequest> authorizationManager;
@Test
void constructorWhenAuthorizationManagerNullThenIllegalArgument() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new AuthorizationManagerWebInvocationPrivilegeEvaluator(null))
.withMessage("authorizationManager cannot be null");
}
@Test
void isAllowedWhenAuthorizationManagerAllowsThenAllowedTrue() {
given(this.authorizationManager.check(any(), any())).willReturn(new AuthorizationDecision(true));
boolean allowed = this.privilegeEvaluator.isAllowed("/test", TestAuthentication.authenticatedUser());
assertThat(allowed).isTrue();
verify(this.authorizationManager).check(any(), any());
}
@Test
void isAllowedWhenAuthorizationManagerDeniesAllowedFalse() {
given(this.authorizationManager.check(any(), any())).willReturn(new AuthorizationDecision(false));
boolean allowed = this.privilegeEvaluator.isAllowed("/test", TestAuthentication.authenticatedUser());
assertThat(allowed).isFalse();
}
}
@@ -0,0 +1,200 @@
/*
* Copyright 2002-2022 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.web.access;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests for {@link RequestMatcherDelegatingWebInvocationPrivilegeEvaluator}
*
* @author Marcus Da Coregio
*/
class RequestMatcherDelegatingWebInvocationPrivilegeEvaluatorTests {
private final RequestMatcher alwaysMatch = mock(RequestMatcher.class);
private final RequestMatcher alwaysDeny = mock(RequestMatcher.class);
private final String uri = "/test";
private final Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");
@BeforeEach
void setup() {
given(this.alwaysMatch.matches(any())).willReturn(true);
given(this.alwaysDeny.matches(any())).willReturn(false);
}
@Test
void isAllowedWhenDelegatesEmptyThenAllowed() {
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator delegating = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
Collections.emptyList());
assertThat(delegating.isAllowed(this.uri, this.authentication)).isTrue();
}
@Test
void isAllowedWhenNotMatchThenAllowed() {
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> notMatch = new RequestMatcherEntry<>(this.alwaysDeny,
Collections.singletonList(TestWebInvocationPrivilegeEvaluator.alwaysAllow()));
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator delegating = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
Collections.singletonList(notMatch));
assertThat(delegating.isAllowed(this.uri, this.authentication)).isTrue();
verify(notMatch.getRequestMatcher()).matches(any());
}
@Test
void isAllowedWhenPrivilegeEvaluatorAllowThenAllowedTrue() {
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> delegate = new RequestMatcherEntry<>(
this.alwaysMatch, Collections.singletonList(TestWebInvocationPrivilegeEvaluator.alwaysAllow()));
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator delegating = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
Collections.singletonList(delegate));
assertThat(delegating.isAllowed(this.uri, this.authentication)).isTrue();
}
@Test
void isAllowedWhenPrivilegeEvaluatorDenyThenAllowedFalse() {
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> delegate = new RequestMatcherEntry<>(
this.alwaysMatch, Collections.singletonList(TestWebInvocationPrivilegeEvaluator.alwaysDeny()));
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator delegating = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
Collections.singletonList(delegate));
assertThat(delegating.isAllowed(this.uri, this.authentication)).isFalse();
}
@Test
void isAllowedWhenNotMatchThenMatchThenOnlySecondDelegateInvoked() {
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> notMatchDelegate = new RequestMatcherEntry<>(
this.alwaysDeny, Collections.singletonList(TestWebInvocationPrivilegeEvaluator.alwaysAllow()));
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> matchDelegate = new RequestMatcherEntry<>(
this.alwaysMatch, Collections.singletonList(TestWebInvocationPrivilegeEvaluator.alwaysAllow()));
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> spyNotMatchDelegate = spy(notMatchDelegate);
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> spyMatchDelegate = spy(matchDelegate);
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator delegating = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
Arrays.asList(notMatchDelegate, spyMatchDelegate));
assertThat(delegating.isAllowed(this.uri, this.authentication)).isTrue();
verify(spyNotMatchDelegate.getRequestMatcher()).matches(any());
verify(spyNotMatchDelegate, never()).getEntry();
verify(spyMatchDelegate.getRequestMatcher()).matches(any());
verify(spyMatchDelegate, times(2)).getEntry(); // 2 times, one for constructor and
// other one in isAllowed
}
@Test
void isAllowedWhenDelegatePrivilegeEvaluatorsEmptyThenAllowedTrue() {
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> delegate = new RequestMatcherEntry<>(
this.alwaysMatch, Collections.emptyList());
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator delegating = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
Collections.singletonList(delegate));
assertThat(delegating.isAllowed(this.uri, this.authentication)).isTrue();
}
@Test
void isAllowedWhenFirstDelegateDenyThenDoNotInvokeOthers() {
WebInvocationPrivilegeEvaluator deny = TestWebInvocationPrivilegeEvaluator.alwaysDeny();
WebInvocationPrivilegeEvaluator allow = TestWebInvocationPrivilegeEvaluator.alwaysAllow();
WebInvocationPrivilegeEvaluator spyDeny = spy(deny);
WebInvocationPrivilegeEvaluator spyAllow = spy(allow);
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> delegate = new RequestMatcherEntry<>(
this.alwaysMatch, Arrays.asList(spyDeny, spyAllow));
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator delegating = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
Collections.singletonList(delegate));
assertThat(delegating.isAllowed(this.uri, this.authentication)).isFalse();
verify(spyDeny).isAllowed(any(), any());
verifyNoInteractions(spyAllow);
}
@Test
void isAllowedWhenDifferentArgumentsThenCallSpecificIsAllowedInDelegate() {
WebInvocationPrivilegeEvaluator deny = TestWebInvocationPrivilegeEvaluator.alwaysDeny();
WebInvocationPrivilegeEvaluator spyDeny = spy(deny);
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> delegate = new RequestMatcherEntry<>(
this.alwaysMatch, Collections.singletonList(spyDeny));
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator delegating = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
Collections.singletonList(delegate));
assertThat(delegating.isAllowed(this.uri, this.authentication)).isFalse();
assertThat(delegating.isAllowed("/cp", this.uri, "GET", this.authentication)).isFalse();
verify(spyDeny).isAllowed(any(), any());
verify(spyDeny).isAllowed(any(), any(), any(), any());
verifyNoMoreInteractions(spyDeny);
}
@Test
void isAllowedWhenServletContextIsSetThenPassedFilterInvocationHttpServletRequestHasServletContext() {
Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX");
MockServletContext servletContext = new MockServletContext();
ArgumentCaptor<HttpServletRequest> argumentCaptor = ArgumentCaptor.forClass(HttpServletRequest.class);
RequestMatcher requestMatcher = mock(RequestMatcher.class);
WebInvocationPrivilegeEvaluator wipe = mock(WebInvocationPrivilegeEvaluator.class);
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> delegate = new RequestMatcherEntry<>(requestMatcher,
Collections.singletonList(wipe));
RequestMatcherDelegatingWebInvocationPrivilegeEvaluator requestMatcherWipe = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
Collections.singletonList(delegate));
requestMatcherWipe.setServletContext(servletContext);
requestMatcherWipe.isAllowed("/foo/index.jsp", token);
verify(requestMatcher).matches(argumentCaptor.capture());
assertThat(argumentCaptor.getValue().getServletContext()).isNotNull();
}
@Test
void constructorWhenPrivilegeEvaluatorsNullThenException() {
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> entry = new RequestMatcherEntry<>(this.alwaysMatch,
null);
assertThatIllegalArgumentException().isThrownBy(
() -> new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(Collections.singletonList(entry)))
.withMessageContaining("webInvocationPrivilegeEvaluators cannot be null");
}
@Test
void constructorWhenRequestMatcherNullThenException() {
RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> entry = new RequestMatcherEntry<>(null,
Collections.singletonList(mock(WebInvocationPrivilegeEvaluator.class)));
assertThatIllegalArgumentException().isThrownBy(
() -> new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(Collections.singletonList(entry)))
.withMessageContaining("requestMatcher cannot be null");
}
}
@@ -0,0 +1,66 @@
/*
* Copyright 2002-2021 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.web.access;
import org.springframework.security.core.Authentication;
public final class TestWebInvocationPrivilegeEvaluator {
private static final AlwaysAllowWebInvocationPrivilegeEvaluator ALWAYS_ALLOW = new AlwaysAllowWebInvocationPrivilegeEvaluator();
private static final AlwaysDenyWebInvocationPrivilegeEvaluator ALWAYS_DENY = new AlwaysDenyWebInvocationPrivilegeEvaluator();
private TestWebInvocationPrivilegeEvaluator() {
}
public static WebInvocationPrivilegeEvaluator alwaysAllow() {
return ALWAYS_ALLOW;
}
public static WebInvocationPrivilegeEvaluator alwaysDeny() {
return ALWAYS_DENY;
}
private static class AlwaysAllowWebInvocationPrivilegeEvaluator implements WebInvocationPrivilegeEvaluator {
@Override
public boolean isAllowed(String uri, Authentication authentication) {
return true;
}
@Override
public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) {
return true;
}
}
private static class AlwaysDenyWebInvocationPrivilegeEvaluator implements WebInvocationPrivilegeEvaluator {
@Override
public boolean isAllowed(String uri, Authentication authentication) {
return false;
}
@Override
public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) {
return false;
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -125,4 +125,11 @@ public class AuthorizationFilterTests {
verifyNoInteractions(mockFilterChain);
}
@Test
public void getAuthorizationManager() {
AuthorizationManager<HttpServletRequest> authorizationManager = mock(AuthorizationManager.class);
AuthorizationFilter authorizationFilter = new AuthorizationFilter(authorizationManager);
assertThat(authorizationFilter.getAuthorizationManager()).isSameAs(authorizationManager);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,6 +16,8 @@
package org.springframework.security.web.server.util.matcher;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -43,6 +45,21 @@ public class MediaTypeServerWebExchangeMatcherTests {
assertThatIllegalArgumentException().isThrownBy(() -> new MediaTypeServerWebExchangeMatcher(types));
}
// gh-10703
@Test
public void constructorListOfDoesNotThrowNullPointerException() {
List<MediaType> mediaTypes = new ArrayList<MediaType>(Arrays.asList(MediaType.ALL)) {
@Override
public boolean contains(Object o) {
if (o == null) {
throw new NullPointerException();
}
return super.contains(o);
}
};
new MediaTypeServerWebExchangeMatcher(mediaTypes);
}
@Test
public void constructorMediaTypeArrayWhenContainsNullThenThrowsIllegalArgumentException() {
MediaType[] types = { null };
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,6 +16,7 @@
package org.springframework.security.web.util.matcher;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -55,6 +56,22 @@ public class AndRequestMatcherTests {
assertThatNullPointerException().isThrownBy(() -> new AndRequestMatcher((RequestMatcher[]) null));
}
// gh-10703
@Test
public void constructorListOfDoesNotThrowNullPointer() {
List<RequestMatcher> requestMatchers = new ArrayList<RequestMatcher>(
Arrays.asList(AnyRequestMatcher.INSTANCE)) {
@Override
public boolean contains(Object o) {
if (o == null) {
throw new NullPointerException();
}
return super.contains(o);
}
};
new AndRequestMatcher(requestMatchers);
}
@Test
public void constructorArrayContainsNull() {
assertThatIllegalArgumentException().isThrownBy(() -> new AndRequestMatcher((RequestMatcher) null));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2022 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.
@@ -16,6 +16,7 @@
package org.springframework.security.web.util.matcher;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -55,6 +56,23 @@ public class OrRequestMatcherTests {
assertThatNullPointerException().isThrownBy(() -> new OrRequestMatcher((RequestMatcher[]) null));
}
// gh-10703
@Test
public void constructorListOfDoesNotThrowNullPointer() {
// emulate List.of for pre-JDK 9 builds
List<RequestMatcher> requestMatchers = new ArrayList<RequestMatcher>(
Arrays.asList(AnyRequestMatcher.INSTANCE)) {
@Override
public boolean contains(Object o) {
if (o == null) {
throw new NullPointerException();
}
return super.contains(o);
}
};
new OrRequestMatcher(requestMatchers);
}
@Test
public void constructorArrayContainsNull() {
assertThatIllegalArgumentException().isThrownBy(() -> new OrRequestMatcher((RequestMatcher) null));
@@ -0,0 +1,41 @@
/*
* Copyright 2002-2021 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.web.util.matcher;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
class RequestMatcherEntryTests {
@Test
void constructWhenGetRequestMatcherAndEntryThenSameRequestMatcherAndEntry() {
RequestMatcher requestMatcher = mock(RequestMatcher.class);
RequestMatcherEntry<String> entry = new RequestMatcherEntry<>(requestMatcher, "entry");
assertThat(entry.getRequestMatcher()).isSameAs(requestMatcher);
assertThat(entry.getEntry()).isEqualTo("entry");
}
@Test
void constructWhenNullValuesThenNullValues() {
RequestMatcherEntry<String> entry = new RequestMatcherEntry<>(null, null);
assertThat(entry.getRequestMatcher()).isNull();
assertThat(entry.getEntry()).isNull();
}
}