Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e9f3da94fc | |||
| 4fb6f936a7 | |||
| c431b5479e | |||
| fc03950afd | |||
| 516b5fed4a | |||
| 165e29e585 | |||
| 0be3e86b21 | |||
| 96d229a6d1 | |||
| 5098cad9cf | |||
| 883c480af0 | |||
| 0f83e40427 | |||
| ad6ef6e4c7 | |||
| 94201e46f1 | |||
| 910e198cee | |||
| 45a88fc391 | |||
| 8f01efb9e3 | |||
| f53c65b3a0 | |||
| 0be772ff5b | |||
| ac990afa5d | |||
| 55cccbf727 | |||
| a095ea75a2 | |||
| a041e7c943 | |||
| 4f3072b3d9 | |||
| 13c467734a | |||
| c01b2b946b | |||
| 549f9dab64 | |||
| 58090c37ea | |||
| 38344e8de6 | |||
| 6f319f0b13 | |||
| 4e71b26ea4 | |||
| 04f3bbcefa | |||
| c67ee6f2a8 | |||
| 4b590ce31f | |||
| 5902b46e9b | |||
| 62449d6fa2 | |||
| aa06088077 | |||
| 2a60776c8a | |||
| f411ecada2 | |||
| 8eeacf4a36 | |||
| 20c252982e | |||
| 6c5ac0d8ec | |||
| e1cb375fbf | |||
| e7e3f06044 | |||
| 994e93741b | |||
| 04e1a11e35 | |||
| 547056d5cc | |||
| 1fa35fb9a8 | |||
| a49914e631 | |||
| 783a43ea35 | |||
| 07a176d11e | |||
| c664fbc1a3 | |||
| af412c4d65 |
@@ -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 }}
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -106,6 +106,7 @@ subprojects {
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = "UTF-8"
|
||||
options.compilerArgs.add("-parameters")
|
||||
options.release = 8
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-2
@@ -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")
|
||||
|
||||
+85
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+99
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+67
@@ -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));
|
||||
}
|
||||
}
|
||||
+41
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
+270
@@ -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'
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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
-1
@@ -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.
|
||||
|
||||
+53
-4
@@ -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)}
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+1
-2
@@ -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) {
|
||||
|
||||
+1
-2
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+191
-5
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-7
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
+14
-4
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
@@ -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
@@ -1,3 +1,2 @@
|
||||
name: ROOT
|
||||
version: '5.6.1'
|
||||
prerelease: '-SNAPSHOT'
|
||||
version: '5.6.2'
|
||||
|
||||
@@ -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 Security’s 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,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
@@ -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"
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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"
|
||||
|
||||
+3
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -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;
|
||||
|
||||
|
||||
+3
@@ -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();
|
||||
|
||||
+3
@@ -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));
|
||||
|
||||
+6
@@ -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);
|
||||
|
||||
+6
@@ -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);
|
||||
|
||||
+3
-1
@@ -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);
|
||||
|
||||
+4
-1
@@ -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,
|
||||
|
||||
+3
-1
@@ -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);
|
||||
|
||||
+4
-1
@@ -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,
|
||||
|
||||
+9
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+16
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+20
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
+2
-3
@@ -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(" ")));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+57
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+132
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+9
-1
@@ -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();
|
||||
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -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
|
||||
*/
|
||||
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+44
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+68
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
+200
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
+66
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+8
-1
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+18
-1
@@ -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 };
|
||||
|
||||
+18
-1
@@ -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));
|
||||
|
||||
+19
-1
@@ -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));
|
||||
|
||||
+41
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user