Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 89c815032c | |||
| ff055cf07a | |||
| b08a06aa76 | |||
| 465d80c162 | |||
| 4992e8ce62 | |||
| c772daab92 | |||
| 45a4a89960 | |||
| b43c7e927f | |||
| 50d23622d0 | |||
| 2c2603ba0f | |||
| f7f53ea2b7 | |||
| e0f8c711d8 | |||
| e0843aabb1 | |||
| 26bb60c567 | |||
| c44230ba24 | |||
| 8d2fb6858f | |||
| 7756247c3a | |||
| 3387149a0f | |||
| 8b74bf9742 | |||
| 40abf87ae6 | |||
| 1c014eb512 | |||
| 8c69699458 | |||
| fc10d5fc29 | |||
| c79ebf4edf | |||
| a8d6c1d21f | |||
| 7c7f9380c7 | |||
| 888715bbb2 | |||
| 13feb87171 | |||
| faf9fb7337 | |||
| f33d7253b6 | |||
| db74e9d128 |
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"index_name": "security-docs",
|
||||
"start_urls": [
|
||||
"https://docs.spring.io/spring-security/reference/"
|
||||
],
|
||||
"selectors": {
|
||||
"lvl0": {
|
||||
"selector": "//nav[@class='crumbs']//li[@class='crumb'][last()-1]",
|
||||
"type": "xpath",
|
||||
"global": true,
|
||||
"default_value": "Home"
|
||||
},
|
||||
"lvl1": ".doc h1",
|
||||
"lvl2": ".doc h2",
|
||||
"lvl3": ".doc h3",
|
||||
"lvl4": ".doc h4",
|
||||
"text": ".doc p, .doc td.content, .doc th.tableblock"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
HOST="$1"
|
||||
HOST_PATH="$2"
|
||||
SSH_PRIVATE_KEY="$3"
|
||||
SSH_KNOWN_HOST="$4"
|
||||
|
||||
|
||||
if [ "$#" -ne 4 ]; then
|
||||
echo -e "not enough arguments USAGE:\n\n$0 \$HOST \$HOST_PATH \$SSH_PRIVATE_KEY \$SSH_KNOWN_HOSTS \n\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use a non-default path to avoid overriding when testing locally
|
||||
SSH_PRIVATE_KEY_PATH=~/.ssh/github-actions-docs
|
||||
install -m 600 -D /dev/null "$SSH_PRIVATE_KEY_PATH"
|
||||
echo "$SSH_PRIVATE_KEY" > "$SSH_PRIVATE_KEY_PATH"
|
||||
echo "$SSH_KNOWN_HOST" > ~/.ssh/known_hosts
|
||||
rsync --delete -avze "ssh -i $SSH_PRIVATE_KEY_PATH" docs/build/site/ "$HOST:$HOST_PATH"
|
||||
rm -f "$SSH_PRIVATE_KEY_PATH"
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
###
|
||||
# Docs
|
||||
# config.json https://docsearch.algolia.com/docs/config-file
|
||||
# Run the crawler https://docsearch.algolia.com/docs/run-your-own/#run-the-crawl-from-the-docker-image
|
||||
|
||||
### USAGE
|
||||
if [ "$#" -ne 3 ]; then
|
||||
echo -e "not enough arguments USAGE:\n\n$0 \$ALGOLIA_APPLICATION_ID \$ALGOLIA_API_KEY \$CONFIG_FILE\n\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Script Parameters
|
||||
APPLICATION_ID=$1
|
||||
API_KEY=$2
|
||||
CONFIG_FILE=$3
|
||||
|
||||
#### Script
|
||||
script_dir=$(dirname $0)
|
||||
docker run -e "APPLICATION_ID=$APPLICATION_ID" -e "API_KEY=$API_KEY" -e "CONFIG=$(cat $CONFIG_FILE | jq -r tostring)" algolia/docsearch-scraper
|
||||
@@ -1,20 +0,0 @@
|
||||
name: Update Algolia Index
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
workflow_dispatch: # Manual trigger
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update:
|
||||
name: Update Algolia Index
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v2
|
||||
- name: Update Index
|
||||
run: ${GITHUB_WORKSPACE}/.github/actions/algolia-docsearch-scraper.sh "${{ secrets.ALGOLIA_APPLICATION_ID }}" "${{ secrets.ALGOLIA_WRITE_API_KEY }}" "${GITHUB_WORKSPACE}/.github/actions/algolia-config.json"
|
||||
@@ -1,33 +0,0 @@
|
||||
name: Backport Bot
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
push:
|
||||
branches:
|
||||
- '*.x'
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: Download BackportBot
|
||||
run: wget https://github.com/spring-io/backport-bot/releases/download/latest/backport-bot-0.0.1-SNAPSHOT.jar
|
||||
- name: Backport
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_EVENT: ${{ toJSON(github.event) }}
|
||||
run: java -jar backport-bot-0.0.1-SNAPSHOT.jar --github.accessToken="$GITHUB_TOKEN" --github.event_name "$GITHUB_EVENT_NAME" --github.event "$GITHUB_EVENT"
|
||||
@@ -98,13 +98,15 @@ jobs:
|
||||
- name: Check samples project
|
||||
env:
|
||||
LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos
|
||||
SAMPLES_INIT_SCRIPT: ${{ github.workspace }}/build/includeRepo/spring-security-ci.gradle
|
||||
SAMPLES_DIR: ../spring-security-samples
|
||||
VERSION: ${{ needs.prerequisites.outputs.project_version }}
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
./gradlew publishMavenJavaPublicationToLocalRepository
|
||||
./gradlew checkSamples -PsamplesInitScript="$SAMPLES_INIT_SCRIPT" -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" --stacktrace
|
||||
./gradlew cloneSamples -PcloneOutputDirectory="$SAMPLES_DIR"
|
||||
./gradlew --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$VERSION" :runAllTests
|
||||
check_tangles:
|
||||
name: Check for Package Tangles
|
||||
needs: [ prerequisites ]
|
||||
@@ -269,7 +271,6 @@ jobs:
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
echo "Updating $REPO@$VERSION to next snapshot version."
|
||||
./gradlew :updateToSnapshotVersion
|
||||
./gradlew :spring-security-docs:antoraUpdateVersion
|
||||
git commit -am "Next development version"
|
||||
git push
|
||||
perform_post_release:
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
name: Build & Deploy Reference
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: request-build-reference
|
||||
schedule:
|
||||
- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
workflow_dispatch: # Manual trigger
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: deploy
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
- name: Cleanup Gradle Cache
|
||||
# Remove some files from the Gradle cache, so they aren't cached by GitHub Actions.
|
||||
# Restoring these files from a GitHub Actions cache might cause problems for future builds.
|
||||
run: |
|
||||
rm -f /home/runner/.gradle/caches/modules-2/modules-2.lock
|
||||
rm -f /home/runner/.gradle/caches/modules-2/gc.properties
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew :spring-security-docs:antora --stacktrace
|
||||
- name: Deploy
|
||||
run: ${GITHUB_WORKSPACE}/.github/actions/algolia-deploy.sh "${{ secrets.DOCS_USERNAME }}@${{ secrets.DOCS_HOST }}" "/opt/www/domains/spring.io/docs/htdocs/spring-security/reference/" "${{ secrets.DOCS_SSH_KEY }}" "${{ secrets.DOCS_SSH_HOST_KEY }}"
|
||||
@@ -64,7 +64,6 @@ jobs:
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
./gradlew :updateProjectVersion
|
||||
./gradlew :spring-security-docs:antoraUpdateVersion
|
||||
updatedVersion=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
|
||||
git commit -am "Release $updatedVersion"
|
||||
git tag $updatedVersion
|
||||
|
||||
+9
-2
@@ -43,9 +43,12 @@ If you're considering anything more than correcting a typo or fixing a minor bug
|
||||
|
||||
If you have not previously done so, please fill out and submit the https://cla.pivotal.io/sign/spring[Contributor License Agreement].
|
||||
|
||||
= Create your branch from main
|
||||
= Create your branch from oldest maintenance branch
|
||||
|
||||
Create your topic branch to be submitted as a pull request from main. The Spring team will consider your pull request for backporting on a case-by-case basis; you don't need to worry about submitting anything for backporting.
|
||||
Create your topic branch to be submitted as a pull request from the oldest impacted and supported maintenance branch.
|
||||
You can find the supported versions by looking at the https://github.com/spring-projects/spring-security/milestones[milestones page].
|
||||
Switch to a branch named `<major>.<minor>.x` from the smallest milestone in the format of `<major>.<minor>.<patch>(-<prerelease>)`.
|
||||
The spring team will ensure the code gets merged forward into additional branches.
|
||||
|
||||
= Use short branch names
|
||||
|
||||
@@ -133,6 +136,10 @@ Search the codebase to find related unit tests and add additional `@Test` method
|
||||
|
||||
Update the https://www.relaxng.org[RELAX NG] schema `spring-security-x.y.rnc` instead of `spring-security-x.y.xsd` if you contribute changes to supported XML configuration. The XML schema file can be generated the following Gradle task:
|
||||
|
||||
----
|
||||
./gradlew :spring-security-config:rncToXsd
|
||||
----
|
||||
|
||||
Changes to the XML schema will be overwritten by the Gradle build task.
|
||||
|
||||
= Squash commits
|
||||
|
||||
+6
-11
@@ -1,3 +1,5 @@
|
||||
import io.spring.gradle.IncludeRepoTask
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "io.spring.javaformat:spring-javaformat-gradle-plugin:$springJavaformatVersion"
|
||||
@@ -16,7 +18,6 @@ apply plugin: 'io.spring.nohttp'
|
||||
apply plugin: 'locks'
|
||||
apply plugin: 's101'
|
||||
apply plugin: 'io.spring.convention.root'
|
||||
apply plugin: 'io.spring.convention.include-check-remote'
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
apply plugin: 'org.springframework.security.update-dependencies'
|
||||
apply plugin: 'org.springframework.security.update-version'
|
||||
@@ -201,16 +202,10 @@ nohttp {
|
||||
|
||||
}
|
||||
|
||||
tasks.register('checkSamples') {
|
||||
includeCheckRemote {
|
||||
repository = 'spring-projects/spring-security-samples'
|
||||
ref = samplesBranch
|
||||
if (project.hasProperty("samplesInitScript")) {
|
||||
initScripts = [samplesInitScript]
|
||||
projectProperties = ["localRepositoryPath": localRepositoryPath, "springSecurityVersion": project.version]
|
||||
}
|
||||
}
|
||||
dependsOn checkRemote
|
||||
tasks.register('cloneSamples', IncludeRepoTask) {
|
||||
repository = 'spring-projects/spring-security-samples'
|
||||
ref = samplesBranch
|
||||
outputDirectory = project.hasProperty("cloneOutputDirectory") ? project.file("$cloneOutputDirectory") : project.file("build/samples")
|
||||
}
|
||||
|
||||
s101 {
|
||||
|
||||
@@ -87,7 +87,7 @@ dependencies {
|
||||
implementation localGroovy()
|
||||
|
||||
implementation 'io.github.gradle-nexus:publish-plugin:1.1.0'
|
||||
implementation 'io.projectreactor:reactor-core:3.4.22'
|
||||
implementation 'io.projectreactor:reactor-core:3.4.24'
|
||||
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'
|
||||
|
||||
@@ -54,7 +54,7 @@ abstract class IncludeRepoTask extends DefaultTask {
|
||||
* Directory where the project template should be copied.
|
||||
*/
|
||||
@OutputDirectory
|
||||
final File outputDirectory = project.file("$project.buildDir/$name")
|
||||
File outputDirectory = project.file("$project.buildDir/$name")
|
||||
|
||||
@TaskAction
|
||||
void checkoutAndCopy() {
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* 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 io.spring.gradle.convention
|
||||
|
||||
import io.spring.gradle.IncludeRepoTask
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.tasks.GradleBuild
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
|
||||
/**
|
||||
* Adds a set of tasks that make easy to clone a remote repository and perform some task
|
||||
*
|
||||
* @author Marcus Da Coregio
|
||||
*/
|
||||
class IncludeCheckRemotePlugin implements Plugin<Project> {
|
||||
@Override
|
||||
void apply(Project project) {
|
||||
IncludeCheckRemoteExtension extension = project.extensions.create('includeCheckRemote', IncludeCheckRemoteExtension)
|
||||
TaskProvider<IncludeRepoTask> includeRepoTask = project.tasks.register('includeRepo', IncludeRepoTask) { IncludeRepoTask it ->
|
||||
it.repository = extension.repository
|
||||
it.ref = extension.ref
|
||||
}
|
||||
project.tasks.register('checkRemote', GradleBuild) {
|
||||
it.dependsOn 'includeRepo'
|
||||
it.dir = includeRepoTask.get().outputDirectory
|
||||
it.tasks = extension.getTasks()
|
||||
extension.getInitScripts().forEach {script ->
|
||||
it.startParameter.addInitScript(new File(script))
|
||||
}
|
||||
extension.getProjectProperties().entrySet().forEach { entry ->
|
||||
it.startParameter.projectProperties.put(entry.getKey(), entry.getValue())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class IncludeCheckRemoteExtension {
|
||||
|
||||
/**
|
||||
* Git repository to clone
|
||||
*/
|
||||
String repository;
|
||||
|
||||
/**
|
||||
* Git ref to checkout
|
||||
*/
|
||||
String ref
|
||||
|
||||
/**
|
||||
* Task to run in the repository
|
||||
*/
|
||||
List<String> tasks = ['check']
|
||||
|
||||
/**
|
||||
* Init scripts for the build
|
||||
*/
|
||||
List<String> initScripts = []
|
||||
|
||||
/**
|
||||
* Map of properties for the build
|
||||
*/
|
||||
Map<String, String> projectProperties = [:]
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
-1
@@ -1 +0,0 @@
|
||||
implementation-class=io.spring.gradle.convention.IncludeCheckRemotePlugin
|
||||
@@ -1,148 +0,0 @@
|
||||
/*
|
||||
* 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 io.spring.gradle.convention;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import io.spring.gradle.IncludeRepoTask;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.tasks.GradleBuild;
|
||||
import org.gradle.testfixtures.ProjectBuilder;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class IncludeCheckRemotePluginTest {
|
||||
|
||||
Project rootProject;
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() throws Exception {
|
||||
if (rootProject != null) {
|
||||
FileUtils.deleteDirectory(rootProject.getProjectDir());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenExtensionPropertiesNoTasksThenCreateCheckRemoteTaskWithDefaultTask() {
|
||||
this.rootProject = ProjectBuilder.builder().build();
|
||||
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
|
||||
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
|
||||
(includeCheckRemoteExtension) -> {
|
||||
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
|
||||
includeCheckRemoteExtension.setProperty("ref", "main");
|
||||
});
|
||||
|
||||
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
|
||||
assertThat(checkRemote.getTasks()).containsExactly("check");
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenExtensionPropertiesTasksThenCreateCheckRemoteWithProvidedTasks() {
|
||||
this.rootProject = ProjectBuilder.builder().build();
|
||||
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
|
||||
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
|
||||
(includeCheckRemoteExtension) -> {
|
||||
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
|
||||
includeCheckRemoteExtension.setProperty("ref", "main");
|
||||
includeCheckRemoteExtension.setProperty("tasks", Arrays.asList("clean", "build", "test"));
|
||||
});
|
||||
|
||||
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
|
||||
assertThat(checkRemote.getTasks()).containsExactly("clean", "build", "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenExtensionPropertiesInitScriptsThenCreateCheckRemoteWithProvidedTasks() {
|
||||
this.rootProject = ProjectBuilder.builder().build();
|
||||
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
|
||||
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
|
||||
(includeCheckRemoteExtension) -> {
|
||||
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
|
||||
includeCheckRemoteExtension.setProperty("ref", "main");
|
||||
includeCheckRemoteExtension.setProperty("initScripts", Arrays.asList("spring-security-ci.gradle"));
|
||||
});
|
||||
|
||||
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
|
||||
assertThat(checkRemote.getStartParameter().getAllInitScripts()).extracting(File::getName).containsExactly("spring-security-ci.gradle");
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenExtensionPropertiesBuildPropertiesThenCreateCheckRemoteWithProvidedTasks() {
|
||||
Map<String, String> projectProperties = new HashMap<>();
|
||||
projectProperties.put("localRepositoryPath", "~/local/repository");
|
||||
projectProperties.put("anotherProperty", "some_value");
|
||||
this.rootProject = ProjectBuilder.builder().build();
|
||||
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
|
||||
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
|
||||
(includeCheckRemoteExtension) -> {
|
||||
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
|
||||
includeCheckRemoteExtension.setProperty("ref", "main");
|
||||
includeCheckRemoteExtension.setProperty("projectProperties", projectProperties);
|
||||
});
|
||||
|
||||
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
|
||||
assertThat(checkRemote.getStartParameter().getProjectProperties()).containsEntry("localRepositoryPath", "~/local/repository")
|
||||
.containsEntry("anotherProperty", "some_value");
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenExtensionPropertiesThenRegisterIncludeRepoTaskWithExtensionProperties() {
|
||||
this.rootProject = ProjectBuilder.builder().build();
|
||||
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
|
||||
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
|
||||
(includeCheckRemoteExtension) -> {
|
||||
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
|
||||
includeCheckRemoteExtension.setProperty("ref", "main");
|
||||
});
|
||||
|
||||
IncludeRepoTask includeRepo = (IncludeRepoTask) this.rootProject.getTasks().named("includeRepo").get();
|
||||
assertThat(includeRepo).isNotNull();
|
||||
assertThat(includeRepo.getRepository().get()).isEqualTo("my-project/my-repository");
|
||||
assertThat(includeRepo.getRef().get()).isEqualTo("main");
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenRegisterTasksThenCheckRemoteDirSameAsIncludeRepoOutputDir() {
|
||||
this.rootProject = ProjectBuilder.builder().build();
|
||||
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
|
||||
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
|
||||
(includeCheckRemoteExtension) -> {
|
||||
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
|
||||
includeCheckRemoteExtension.setProperty("ref", "main");
|
||||
});
|
||||
IncludeRepoTask includeRepo = (IncludeRepoTask) this.rootProject.getTasks().named("includeRepo").get();
|
||||
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
|
||||
assertThat(checkRemote.getDir()).isEqualTo(includeRepo.getOutputDirectory());
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenNoExtensionPropertiesThenRegisterTasks() {
|
||||
this.rootProject = ProjectBuilder.builder().build();
|
||||
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
|
||||
IncludeRepoTask includeRepo = (IncludeRepoTask) this.rootProject.getTasks().named("includeRepo").get();
|
||||
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
|
||||
assertThat(includeRepo).isNotNull();
|
||||
assertThat(checkRemote).isNotNull();
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -4,6 +4,7 @@ import java.nio.charset.Charset;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
@@ -636,7 +637,7 @@ public class GitHubMilestoneApiTests {
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
|
||||
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
|
||||
" \"due_on\":\"" + Instant.now().toString() + "\",\n" +
|
||||
" \"due_on\":\"" + LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant().toString() + "\",\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ public class NamespaceLdapAuthenticationProviderTestsConfigs {
|
||||
.ldif("classpath:users.xldif") // ldap-server@ldif
|
||||
.managerDn("uid=admin,ou=system") // ldap-server@manager-dn
|
||||
.managerPassword("secret") // ldap-server@manager-password
|
||||
.port(33399) // ldap-server@port
|
||||
.port(0) // ldap-server@port
|
||||
.root("dc=springframework,dc=org"); // ldap-server@root
|
||||
// .url("ldap://localhost:33389/dc-springframework,dc=org") this overrides root and port and is used for external
|
||||
// @formatter:on
|
||||
|
||||
+10
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 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.
|
||||
@@ -39,6 +39,7 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.security.authentication.AuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
|
||||
@@ -79,8 +80,7 @@ public class AuthenticationConfiguration {
|
||||
public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor,
|
||||
ApplicationContext context) {
|
||||
LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
|
||||
AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context,
|
||||
AuthenticationEventPublisher.class);
|
||||
AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context);
|
||||
DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(
|
||||
objectPostProcessor, defaultPasswordEncoder);
|
||||
if (authenticationEventPublisher != null) {
|
||||
@@ -142,6 +142,13 @@ public class AuthenticationConfiguration {
|
||||
this.objectPostProcessor = objectPostProcessor;
|
||||
}
|
||||
|
||||
private AuthenticationEventPublisher getAuthenticationEventPublisher(ApplicationContext context) {
|
||||
if (context.getBeanNamesForType(AuthenticationEventPublisher.class).length > 0) {
|
||||
return context.getBean(AuthenticationEventPublisher.class);
|
||||
}
|
||||
return this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T lazyBean(Class<T> interfaceName) {
|
||||
LazyInitTargetSource lazyTargetSource = new LazyInitTargetSource();
|
||||
|
||||
+11
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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.
|
||||
@@ -24,7 +24,9 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.security.authentication.AuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
@@ -82,6 +84,7 @@ class HttpSecurityConfiguration {
|
||||
AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
|
||||
this.objectPostProcessor, passwordEncoder);
|
||||
authenticationBuilder.parentAuthenticationManager(authenticationManager());
|
||||
authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
|
||||
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
|
||||
// @formatter:off
|
||||
http
|
||||
@@ -105,6 +108,13 @@ class HttpSecurityConfiguration {
|
||||
: this.authenticationConfiguration.getAuthenticationManager();
|
||||
}
|
||||
|
||||
private AuthenticationEventPublisher getAuthenticationEventPublisher() {
|
||||
if (this.context.getBeanNamesForType(AuthenticationEventPublisher.class).length > 0) {
|
||||
return this.context.getBean(AuthenticationEventPublisher.class);
|
||||
}
|
||||
return this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
|
||||
}
|
||||
|
||||
private Map<Class<?>, Object> createSharedObjects() {
|
||||
Map<Class<?>, Object> sharedObjects = new HashMap<>();
|
||||
sharedObjects.put(ApplicationContext.class, this.context);
|
||||
|
||||
+50
@@ -34,8 +34,10 @@ import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.authentication.AuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.TestAuthentication;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
@@ -51,6 +53,7 @@ import org.springframework.security.config.annotation.web.servlet.configuration.
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.config.users.AuthenticationTestConfiguration;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
@@ -62,6 +65,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -294,6 +298,28 @@ public class AuthenticationConfigurationTests {
|
||||
assertThatExceptionOfType(AlreadyBuiltException.class).isThrownBy(ap::build);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenDefaultsThenDefaultAuthenticationEventPublisher() {
|
||||
this.spring.register(AuthenticationConfiguration.class, ObjectPostProcessorConfiguration.class).autowire();
|
||||
AuthenticationManagerBuilder authenticationManagerBuilder = this.spring.getContext()
|
||||
.getBean(AuthenticationManagerBuilder.class);
|
||||
AuthenticationEventPublisher eventPublisher = (AuthenticationEventPublisher) ReflectionTestUtils
|
||||
.getField(authenticationManagerBuilder, "eventPublisher");
|
||||
assertThat(eventPublisher).isInstanceOf(DefaultAuthenticationEventPublisher.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenCustomAuthenticationEventPublisherThenCustomAuthenticationEventPublisher() {
|
||||
this.spring.register(AuthenticationConfiguration.class, ObjectPostProcessorConfiguration.class,
|
||||
CustomAuthenticationEventPublisherConfig.class).autowire();
|
||||
AuthenticationManagerBuilder authenticationManagerBuilder = this.spring.getContext()
|
||||
.getBean(AuthenticationManagerBuilder.class);
|
||||
AuthenticationEventPublisher eventPublisher = (AuthenticationEventPublisher) ReflectionTestUtils
|
||||
.getField(authenticationManagerBuilder, "eventPublisher");
|
||||
assertThat(eventPublisher)
|
||||
.isInstanceOf(CustomAuthenticationEventPublisherConfig.MyAuthenticationEventPublisher.class);
|
||||
}
|
||||
|
||||
@EnableGlobalMethodSecurity(securedEnabled = true)
|
||||
static class GlobalMethodSecurityAutowiredConfig {
|
||||
|
||||
@@ -346,6 +372,30 @@ public class AuthenticationConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomAuthenticationEventPublisherConfig {
|
||||
|
||||
@Bean
|
||||
AuthenticationEventPublisher eventPublisher() {
|
||||
return new MyAuthenticationEventPublisher();
|
||||
}
|
||||
|
||||
static class MyAuthenticationEventPublisher implements AuthenticationEventPublisher {
|
||||
|
||||
@Override
|
||||
public void publishAuthenticationSuccess(Authentication authentication) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface Service {
|
||||
|
||||
void run();
|
||||
|
||||
+102
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 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.config.annotation.web.configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -27,12 +29,19 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.mock.web.MockHttpSession;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.AuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
|
||||
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
|
||||
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
@@ -48,6 +57,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
||||
@@ -200,6 +210,48 @@ public class HttpSecurityConfigurationTests {
|
||||
this.mockMvc.perform(get("/login?logout")).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginWhenUsingDefaultThenAuthenticationEventPublished() throws Exception {
|
||||
this.spring
|
||||
.register(SecurityEnabledConfig.class, UserDetailsConfig.class, AuthenticationEventListenerConfig.class)
|
||||
.autowire();
|
||||
AuthenticationEventListenerConfig.clearEvents();
|
||||
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
|
||||
assertThat(AuthenticationEventListenerConfig.EVENTS).isNotEmpty();
|
||||
assertThat(AuthenticationEventListenerConfig.EVENTS).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginWhenUsingDefaultAndNoUserDetailsServiceThenAuthenticationEventPublished() throws Exception {
|
||||
this.spring
|
||||
.register(SecurityEnabledConfig.class, UserDetailsConfig.class, AuthenticationEventListenerConfig.class)
|
||||
.autowire();
|
||||
AuthenticationEventListenerConfig.clearEvents();
|
||||
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
|
||||
assertThat(AuthenticationEventListenerConfig.EVENTS).isNotEmpty();
|
||||
assertThat(AuthenticationEventListenerConfig.EVENTS).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginWhenUsingCustomAuthenticationEventPublisherThenAuthenticationEventPublished() throws Exception {
|
||||
this.spring.register(SecurityEnabledConfig.class, UserDetailsConfig.class,
|
||||
CustomAuthenticationEventPublisherConfig.class).autowire();
|
||||
CustomAuthenticationEventPublisherConfig.clearEvents();
|
||||
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
|
||||
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).isNotEmpty();
|
||||
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginWhenUsingCustomAuthenticationEventPublisherAndNoUserDetailsServiceThenAuthenticationEventPublished()
|
||||
throws Exception {
|
||||
this.spring.register(SecurityEnabledConfig.class, CustomAuthenticationEventPublisherConfig.class).autowire();
|
||||
CustomAuthenticationEventPublisherConfig.clearEvents();
|
||||
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
|
||||
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).isNotEmpty();
|
||||
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).hasSize(1);
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class NameController {
|
||||
|
||||
@@ -270,6 +322,55 @@ public class HttpSecurityConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomAuthenticationEventPublisherConfig {
|
||||
|
||||
static List<Authentication> EVENTS = new ArrayList<>();
|
||||
|
||||
static void clearEvents() {
|
||||
EVENTS.clear();
|
||||
}
|
||||
|
||||
@Bean
|
||||
AuthenticationEventPublisher publisher() {
|
||||
return new AuthenticationEventPublisher() {
|
||||
|
||||
@Override
|
||||
public void publishAuthenticationSuccess(Authentication authentication) {
|
||||
EVENTS.add(authentication);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishAuthenticationFailure(AuthenticationException exception,
|
||||
Authentication authentication) {
|
||||
EVENTS.add(authentication);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class AuthenticationEventListenerConfig {
|
||||
|
||||
static List<AbstractAuthenticationEvent> EVENTS = new ArrayList<>();
|
||||
|
||||
static void clearEvents() {
|
||||
EVENTS.clear();
|
||||
}
|
||||
|
||||
@EventListener
|
||||
void onAuthenticationSuccessEvent(AuthenticationSuccessEvent event) {
|
||||
EVENTS.add(event);
|
||||
}
|
||||
|
||||
@EventListener
|
||||
void onAuthenticationFailureEvent(AbstractAuthenticationFailureEvent event) {
|
||||
EVENTS.add(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class BaseController {
|
||||
|
||||
|
||||
+7
-7
@@ -8,13 +8,13 @@ javaPlatform {
|
||||
|
||||
dependencies {
|
||||
api platform("org.springframework:spring-framework-bom:$springFrameworkVersion")
|
||||
api platform("io.projectreactor:reactor-bom:2020.0.22")
|
||||
api platform("io.rsocket:rsocket-bom:1.1.2")
|
||||
api platform("io.projectreactor:reactor-bom:2020.0.24")
|
||||
api platform("io.rsocket:rsocket-bom:1.1.3")
|
||||
api platform("org.junit:junit-bom:5.8.2")
|
||||
api platform("org.springframework.data:spring-data-bom:2021.1.6")
|
||||
api platform("org.springframework.data:spring-data-bom:2021.1.8")
|
||||
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.3")
|
||||
api platform("com.fasterxml.jackson:jackson-bom:2.13.4.20221012")
|
||||
constraints {
|
||||
api "ch.qos.logback:logback-classic:1.2.11"
|
||||
api "com.google.inject:guice:3.0"
|
||||
@@ -50,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.48.v20220622"
|
||||
api "org.eclipse.jetty:jetty-servlet:9.4.48.v20220622"
|
||||
api "org.eclipse.jetty:jetty-server:9.4.49.v20220914"
|
||||
api "org.eclipse.jetty:jetty-servlet:9.4.49.v20220914"
|
||||
api "org.eclipse.persistence:javax.persistence:2.2.1"
|
||||
api "org.hamcrest:hamcrest:2.2"
|
||||
api "org.hibernate:hibernate-entitymanager:5.6.10.Final"
|
||||
api "org.hibernate:hibernate-entitymanager:5.6.12.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"
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/package-lock.json
|
||||
/node_modules/
|
||||
@@ -0,0 +1,53 @@
|
||||
'use strict'
|
||||
|
||||
/* Copyright (c) 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.
|
||||
*/
|
||||
const { promises: fsp } = require('fs')
|
||||
const ospath = require('path')
|
||||
|
||||
/**
|
||||
* Rewrites local content sources to support the use of linked worktrees.
|
||||
*
|
||||
* @author Dan Allen <dan@opendevise.com>
|
||||
*/
|
||||
module.exports.register = function () {
|
||||
this.once('playbookBuilt', async ({ playbook }) => {
|
||||
const expandPath = this.require('@antora/expand-path-helper')
|
||||
for (const contentSource of playbook.content.sources) {
|
||||
const { url, branches } = contentSource
|
||||
if (url.charAt() !== '.') continue
|
||||
const absdir = expandPath(url, { dot: playbook.dir })
|
||||
const gitfile = ospath.join(absdir, '.git')
|
||||
if (await fsp.stat(gitfile).then((stat) => !stat.isDirectory(), () => false)) {
|
||||
const worktreeGitdir = await fsp.readFile(gitfile, 'utf8')
|
||||
.then((contents) => contents.trimRight().substr(8))
|
||||
const worktreeBranch = await fsp.readFile(ospath.join(worktreeGitdir, 'HEAD'), 'utf8')
|
||||
.then((contents) => contents.trimRight().replace(/^ref: (?:refs\/heads\/)?/, ''))
|
||||
const reldir = ospath.relative(
|
||||
playbook.dir,
|
||||
await fsp.readFile(ospath.join(worktreeGitdir, 'commondir'), 'utf8')
|
||||
.then((contents) => {
|
||||
const gitdir = ospath.join(worktreeGitdir, contents.trimRight())
|
||||
return ospath.basename(gitdir) === '.git' ? ospath.dirname(gitdir) : gitdir
|
||||
})
|
||||
)
|
||||
contentSource.url = reldir ? `.${ospath.sep}${reldir}` : '.'
|
||||
if (!branches) continue
|
||||
contentSource.branches = (branches.constructor === Array ? branches : [branches])
|
||||
.map((pattern) => pattern.replaceAll('HEAD', worktreeBranch))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
site:
|
||||
title: Spring Security
|
||||
url: https://docs.spring.io/spring-security/reference/
|
||||
asciidoc:
|
||||
attributes:
|
||||
page-pagination: true
|
||||
content:
|
||||
sources:
|
||||
- url: https://github.com/spring-io/spring-generated-docs
|
||||
branches: [spring-projects/spring-security/*]
|
||||
- url: https://github.com/spring-projects/spring-security
|
||||
branches: [main,5.6.x]
|
||||
start_path: docs
|
||||
urls:
|
||||
latest_version_segment_strategy: redirect:to
|
||||
latest_version_segment: ''
|
||||
redirect_facility: httpd
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/latest/ui-bundle.zip
|
||||
snapshot: true
|
||||
|
||||
pipeline:
|
||||
extensions:
|
||||
- require: ./antora/extensions/major-minor-segment.js
|
||||
- require: ./antora/extensions/root-component-name.js
|
||||
+12
-2
@@ -1,2 +1,12 @@
|
||||
'name': 'ROOT'
|
||||
'version': '5.6.7'
|
||||
name: ROOT
|
||||
version: true
|
||||
title: Documentation
|
||||
nav:
|
||||
- modules/ROOT/nav.adoc
|
||||
ext:
|
||||
collector:
|
||||
run:
|
||||
command: gradlew -q -PbuildSrc.skipTests=true "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :spring-security-docs:generateAntora
|
||||
local: true
|
||||
scan:
|
||||
dir: ./build/generateAntora
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
// https://gitlab.com/antora/antora/-/issues/132#note_712132072
|
||||
'use strict'
|
||||
|
||||
const { posix: path } = require('path')
|
||||
|
||||
module.exports.register = (pipeline, { config }) => {
|
||||
pipeline.on('contentClassified', ({ contentCatalog }) => {
|
||||
contentCatalog.getComponents().forEach(component => {
|
||||
const componentName = component.name;
|
||||
const generationToVersion = new Map();
|
||||
component.versions.forEach(version => {
|
||||
const generation = getGeneration(version.version);
|
||||
const original = generationToVersion.get(generation);
|
||||
if (original === undefined || (original.prerelease && !version.prerelease)) {
|
||||
generationToVersion.set(generation, version);
|
||||
}
|
||||
});
|
||||
|
||||
const versionToGeneration = Array.from(generationToVersion.entries()).reduce((acc, entry) => {
|
||||
const [ generation, version ] = entry;
|
||||
acc.set(version.version, generation);
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
||||
contentCatalog.findBy({ component: componentName }).forEach((file) => {
|
||||
const candidateVersion = file.src.version;
|
||||
if (versionToGeneration.has(candidateVersion)) {
|
||||
const generation = versionToGeneration.get(candidateVersion);
|
||||
if (file.out) {
|
||||
if (file.out) {
|
||||
file.out.dirname = file.out.dirname.replace(candidateVersion, generation)
|
||||
file.out.path = file.out.path.replace(candidateVersion, generation);
|
||||
}
|
||||
}
|
||||
if (file.pub) {
|
||||
file.pub.url = file.pub.url.replace(candidateVersion, generation)
|
||||
}
|
||||
}
|
||||
});
|
||||
versionToGeneration.forEach((generation, mappedVersion) => {
|
||||
contentCatalog.getComponent(componentName).versions.filter(version => version.version === mappedVersion).forEach((version) => {
|
||||
version.url = version.url.replace(mappedVersion, generation);
|
||||
})
|
||||
const symbolicVersionAlias = createSymbolicVersionAlias(
|
||||
componentName,
|
||||
mappedVersion,
|
||||
generation,
|
||||
'redirect:to'
|
||||
)
|
||||
symbolicVersionAlias.src.version = generation;
|
||||
contentCatalog.addFile(symbolicVersionAlias);
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function createSymbolicVersionAlias (component, version, symbolicVersionSegment, strategy) {
|
||||
if (symbolicVersionSegment == null || symbolicVersionSegment === version) return
|
||||
const family = 'alias'
|
||||
const baseVersionAliasSrc = { component, module: 'ROOT', family, relative: '', basename: '', stem: '', extname: '' }
|
||||
const symbolicVersionAliasSrc = Object.assign({}, baseVersionAliasSrc, { version: symbolicVersionSegment })
|
||||
const symbolicVersionAlias = {
|
||||
src: symbolicVersionAliasSrc,
|
||||
pub: computePub(
|
||||
symbolicVersionAliasSrc,
|
||||
computeOut(symbolicVersionAliasSrc, family, symbolicVersionSegment),
|
||||
family
|
||||
),
|
||||
}
|
||||
const originalVersionAliasSrc = Object.assign({}, baseVersionAliasSrc, { version })
|
||||
const originalVersionSegment = computeVersionSegment(component, version, 'original')
|
||||
const originalVersionAlias = {
|
||||
src: originalVersionAliasSrc,
|
||||
pub: computePub(
|
||||
originalVersionAliasSrc,
|
||||
computeOut(originalVersionAliasSrc, family, originalVersionSegment),
|
||||
family
|
||||
),
|
||||
}
|
||||
if (strategy === 'redirect:to') {
|
||||
originalVersionAlias.out = undefined
|
||||
originalVersionAlias.rel = symbolicVersionAlias
|
||||
return originalVersionAlias
|
||||
} else {
|
||||
symbolicVersionAlias.out = undefined
|
||||
symbolicVersionAlias.rel = originalVersionAlias
|
||||
return symbolicVersionAlias
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function computeOut (src, family, version, htmlUrlExtensionStyle) {
|
||||
let { component, module: module_, basename, extname, relative, stem } = src
|
||||
if (module_ === 'ROOT') module_ = ''
|
||||
let indexifyPathSegment = ''
|
||||
let familyPathSegment = ''
|
||||
|
||||
if (family === 'page') {
|
||||
if (stem !== 'index' && htmlUrlExtensionStyle === 'indexify') {
|
||||
basename = 'index.html'
|
||||
indexifyPathSegment = stem
|
||||
} else if (extname === '.adoc') {
|
||||
basename = stem + '.html'
|
||||
}
|
||||
} else if (family === 'image') {
|
||||
familyPathSegment = '_images'
|
||||
} else if (family === 'attachment') {
|
||||
familyPathSegment = '_attachments'
|
||||
}
|
||||
const modulePath = path.join(component, version, module_)
|
||||
const dirname = path.join(modulePath, familyPathSegment, path.dirname(relative), indexifyPathSegment)
|
||||
const path_ = path.join(dirname, basename)
|
||||
const moduleRootPath = path.relative(dirname, modulePath) || '.'
|
||||
const rootPath = path.relative(dirname, '') || '.'
|
||||
|
||||
return { dirname, basename, path: path_, moduleRootPath, rootPath }
|
||||
}
|
||||
|
||||
function computePub (src, out, family, version, htmlUrlExtensionStyle) {
|
||||
const pub = {}
|
||||
let url
|
||||
if (family === 'nav') {
|
||||
const urlSegments = version ? [src.component, version] : [src.component]
|
||||
if (src.module && src.module !== 'ROOT') urlSegments.push(src.module)
|
||||
// an artificial URL used for resolving page references in navigation model
|
||||
url = '/' + urlSegments.join('/') + '/'
|
||||
pub.moduleRootPath = '.'
|
||||
} else if (family === 'page') {
|
||||
const urlSegments = out.path.split('/')
|
||||
const lastUrlSegmentIdx = urlSegments.length - 1
|
||||
if (htmlUrlExtensionStyle === 'drop') {
|
||||
// drop just the .html extension or, if the filename is index.html, the whole segment
|
||||
const lastUrlSegment = urlSegments[lastUrlSegmentIdx]
|
||||
urlSegments[lastUrlSegmentIdx] =
|
||||
lastUrlSegment === 'index.html' ? '' : lastUrlSegment.substr(0, lastUrlSegment.length - 5)
|
||||
} else if (htmlUrlExtensionStyle === 'indexify') {
|
||||
urlSegments[lastUrlSegmentIdx] = ''
|
||||
}
|
||||
url = '/' + urlSegments.join('/')
|
||||
} else {
|
||||
url = '/' + out.path
|
||||
if (family === 'alias' && !src.relative.length) pub.splat = true
|
||||
}
|
||||
|
||||
pub.url = ~url.indexOf(' ') ? url.replace(SPACE_RX, '%20') : url
|
||||
|
||||
if (out) {
|
||||
pub.moduleRootPath = out.moduleRootPath
|
||||
pub.rootPath = out.rootPath
|
||||
}
|
||||
|
||||
return pub
|
||||
}
|
||||
|
||||
function computeVersionSegment (name, version, mode) {
|
||||
if (mode === 'original') return !version || version === 'master' ? '' : version
|
||||
const strategy = this.latestVersionUrlSegmentStrategy
|
||||
// NOTE: special exception; revisit in Antora 3
|
||||
if (!version || version === 'master') {
|
||||
if (mode !== 'alias') return ''
|
||||
if (strategy === 'redirect:to') return
|
||||
}
|
||||
if (strategy === 'redirect:to' || strategy === (mode === 'alias' ? 'redirect:from' : 'replace')) {
|
||||
const component = this.getComponent(name)
|
||||
const componentVersion = component && this.getComponentVersion(component, version)
|
||||
if (componentVersion) {
|
||||
const segment =
|
||||
componentVersion === component.latest
|
||||
? this.latestVersionUrlSegment
|
||||
: componentVersion === component.latestPrerelease
|
||||
? this.latestPrereleaseVersionUrlSegment
|
||||
: undefined
|
||||
return segment == null ? version : segment
|
||||
}
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
function getGeneration(version) {
|
||||
if (!version) return version;
|
||||
const firstIndex = version.indexOf('.')
|
||||
if (firstIndex < 0) {
|
||||
return version;
|
||||
}
|
||||
const secondIndex = version.indexOf('.', firstIndex + 1);
|
||||
const result = version.substr(0, secondIndex);
|
||||
return result;
|
||||
}
|
||||
|
||||
function out(args) {
|
||||
console.log(JSON.stringify(args, no_data, 2));
|
||||
}
|
||||
|
||||
|
||||
function no_data(key, value) {
|
||||
if (key == "data" || key == "files") {
|
||||
return value ? "__data__" : value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
// https://gitlab.com/antora/antora/-/issues/132#note_712132072
|
||||
'use strict'
|
||||
|
||||
const { posix: path } = require('path')
|
||||
|
||||
module.exports.register = (pipeline, { config }) => {
|
||||
pipeline.on('contentClassified', ({ contentCatalog }) => {
|
||||
const rootComponentName = config.rootComponentName || 'ROOT'
|
||||
const rootComponentNameLength = rootComponentName.length
|
||||
contentCatalog.findBy({ component: rootComponentName }).forEach((file) => {
|
||||
if (file.out) {
|
||||
file.out.dirname = file.out.dirname.substr(rootComponentNameLength)
|
||||
file.out.path = file.out.path.substr(rootComponentNameLength + 1)
|
||||
file.out.rootPath = fixPath(file.out.rootPath)
|
||||
}
|
||||
if (file.pub) {
|
||||
file.pub.url = file.pub.url.substr(rootComponentNameLength + 1)
|
||||
if (file.pub.rootPath) {
|
||||
file.pub.rootPath = fixPath(file.pub.rootPath)
|
||||
}
|
||||
}
|
||||
if (file.rel) {
|
||||
if (file.rel.pub) {
|
||||
file.rel.pub.url = file.rel.pub.url.substr(rootComponentNameLength + 1)
|
||||
file.rel.pub.rootPath = fixPath(file.rel.pub.rootPath);
|
||||
}
|
||||
}
|
||||
})
|
||||
const rootComponent = contentCatalog.getComponent(rootComponentName)
|
||||
rootComponent?.versions?.forEach((version) => {
|
||||
version.url = version.url.substr(rootComponentName.length + 1)
|
||||
})
|
||||
// const siteStartPage = contentCatalog.getById({ component: '', version: '', module: '', family: 'alias', relative: 'index.adoc' })
|
||||
// if (siteStartPage) delete siteStartPage.out
|
||||
})
|
||||
|
||||
function fixPath(path) {
|
||||
return path.split('/').slice(1).join('/') || '.'
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,24 @@
|
||||
# The purpose of this Antora playbook is to generate a preview of the docs in the current branch.
|
||||
antora:
|
||||
extensions:
|
||||
- ./antora-linked-worktree-patch.js
|
||||
- '@antora/collector-extension'
|
||||
site:
|
||||
title: Spring Security
|
||||
url: https://docs.spring.io/spring-security/reference/
|
||||
asciidoc:
|
||||
attributes:
|
||||
page-pagination: true
|
||||
url: https://docs.spring.io/spring-security/reference
|
||||
content:
|
||||
sources:
|
||||
- url: ../../spring-io/spring-generated-docs
|
||||
branches: [spring-projects/spring-security/*]
|
||||
- url: ../../spring-projects/spring-security
|
||||
branches: [main,5.6.x]
|
||||
start_path: docs
|
||||
- url: ./..
|
||||
branches: HEAD
|
||||
start_path: docs
|
||||
worktrees: true
|
||||
asciidoc:
|
||||
attributes:
|
||||
page-pagination: ''
|
||||
hide-uri-scheme: '@'
|
||||
urls:
|
||||
latest_version_segment_strategy: redirect:to
|
||||
latest_version_segment: ''
|
||||
redirect_facility: httpd
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/latest/ui-bundle.zip
|
||||
snapshot: true
|
||||
|
||||
pipeline:
|
||||
extensions:
|
||||
- require: ./antora/extensions/major-minor-segment.js
|
||||
- require: ./antora/extensions/root-component-name.js
|
||||
|
||||
@@ -278,7 +278,7 @@ If you use additional features (such as LDAP, OpenID, and others), you need to a
|
||||
|
||||
Spring Security builds against Spring Framework {spring-core-version} but should generally work with any newer version of Spring Framework 5.x.
|
||||
Many users are likely to run afoul of the fact that Spring Security's transitive dependencies resolve Spring Framework {spring-core-version}, which can cause strange classpath problems.
|
||||
The easiest way to resolve this is to use the `spring-framework-bom` within your `<dependencyManagement>` section of your `pom.xml`.
|
||||
The easiest way to resolve this is to use the `spring-framework-bom` within your `dependencyManagement` section of your `build.gradle`.
|
||||
You can do so by using the https://github.com/spring-gradle-plugins/dependency-management-plugin[Dependency Management Plugin], as the following example shows:
|
||||
|
||||
.build.gradle
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
= Authentication
|
||||
|
||||
Spring Security provides comprehensive support for xref:features/authentication/index.adoc#authentication[Authentication].
|
||||
We start by discussing the overall xref:servlet/architecture.adoc#servlet-architecture[Servlet Authentication Architecture].
|
||||
We start by discussing the overall xref:servlet/authentication/architecture.adoc[Servlet Authentication Architecture].
|
||||
As you might expect, this section is more abstract describing the architecture without much discussion on how it applies to concrete flows.
|
||||
|
||||
If you prefer, you can refer to <<servlet-authentication-mechanisms,Authentication Mechanisms>> for concrete ways in which users can authenticate.
|
||||
|
||||
@@ -1,53 +1,56 @@
|
||||
plugins {
|
||||
id "io.github.rwinch.antora" version "0.0.2"
|
||||
id "org.springframework.antora.check-version"
|
||||
id 'org.antora' version '1.0.0-alpha.3'
|
||||
}
|
||||
|
||||
apply plugin: 'io.spring.convention.docs'
|
||||
apply plugin: 'java'
|
||||
|
||||
antora {
|
||||
antoraVersion = "3.0.0-alpha.8"
|
||||
arguments = ["--fetch"]
|
||||
}
|
||||
|
||||
tasks.antora {
|
||||
version = '~3.1'
|
||||
playbook = file('local-antora-playbook.yml')
|
||||
options = ['--clean', '--stacktrace']
|
||||
environment = [
|
||||
"ALGOLIA_API_KEY" : "82c7ead946afbac3cf98c32446154691",
|
||||
"ALGOLIA_APP_ID" : "244V8V9FGG",
|
||||
"ALGOLIA_INDEX_NAME" : "security-docs"
|
||||
'ALGOLIA_API_KEY': '82c7ead946afbac3cf98c32446154691',
|
||||
'ALGOLIA_APP_ID': '244V8V9FGG',
|
||||
'ALGOLIA_INDEX_NAME': 'security-docs'
|
||||
]
|
||||
dependencies = [
|
||||
'@antora/collector-extension': '1.0.0-alpha.2'
|
||||
]
|
||||
}
|
||||
|
||||
tasks.register("generateAntora") {
|
||||
group = "Documentation"
|
||||
description = "Generates the antora.yml for dynamic properties"
|
||||
tasks.register('generateAntora') {
|
||||
group = 'Documentation'
|
||||
description = 'Generates the antora.yml for dynamic properties'
|
||||
doLast {
|
||||
def docsTag = snapshotBuild ? 'current' : project.version
|
||||
def ghTag = snapshotBuild ? 'main' : project.version
|
||||
def ghUrl = "https://github.com/spring-projects/spring-security/tree/$ghTag"
|
||||
def ghOldSamplesUrl = "https://github.com/spring-projects/spring-security/tree/5.4.x/samples"
|
||||
def ghOldSamplesUrl = 'https://github.com/spring-projects/spring-security/tree/5.4.x/samples'
|
||||
def ghSamplesUrl = "https://github.com/spring-projects/spring-security-samples/tree/$samplesBranch"
|
||||
def securityDocsUrl = "https://docs.spring.io/spring-security/site/docs/$docsTag"
|
||||
def securityApiUrl = "$securityDocsUrl/api/"
|
||||
def securityReferenceUrl = "$securityDocsUrl/reference/html5/"
|
||||
def springFrameworkApiUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/javadoc-api/"
|
||||
def springFrameworkReferenceUrl = "https://docs.spring.io/spring-framework/docs/$springFrameworkVersion/reference/html/"
|
||||
def versions = resolvedVersions(project.configurations.testRuntimeClasspath)
|
||||
def ymlVersions = ""
|
||||
versions.call().each { name, version ->
|
||||
ymlVersions += """
|
||||
${name}: ${version}"""
|
||||
}
|
||||
def outputFile = new File("$buildDir/generateAntora/antora.yml")
|
||||
outputFile.getParentFile().mkdirs()
|
||||
outputFile.createNewFile()
|
||||
def antoraYmlText = file("antora.yml").getText()
|
||||
outputFile.setText("""$antoraYmlText
|
||||
title: Spring Security
|
||||
start_page: ROOT:index.adoc
|
||||
nav:
|
||||
- modules/ROOT/nav.adoc
|
||||
def ymlVersions = resolvedVersions(project.configurations.testRuntimeClasspath).call()
|
||||
.collect(v -> " ${v.getKey()}: ${v.getValue()}")
|
||||
.join('\n')
|
||||
def outputFile = layout.buildDirectory.file('generateAntora/antora.yml').get().asFile
|
||||
mkdir(outputFile.getParentFile())
|
||||
def components = project.version.split(/(?=-)/)
|
||||
def mainVersion = components[0];
|
||||
def prerelease = components.length > 1 ? components[1] : null
|
||||
def antoraYmlText = file('antora.yml').text
|
||||
layout.buildDirectory.file('.antora.yml').get().asFile.text = antoraYmlText
|
||||
antoraYmlText = antoraYmlText.lines().collect { l ->
|
||||
if (l.startsWith('version: ')) {
|
||||
return prerelease == null ? "version: '${mainVersion}'" : "version: '${mainVersion}'\nprerelease: '${prerelease}'"
|
||||
}
|
||||
if (l.startsWith('title: ')) return "title: ${project.parent.description}"
|
||||
return l == 'ext:' || l.getAt(0) == ' ' ? null : l
|
||||
}.findAll(Objects::nonNull).join('\n')
|
||||
outputFile.text = """$antoraYmlText
|
||||
asciidoc:
|
||||
attributes:
|
||||
icondir: icons
|
||||
@@ -60,30 +63,28 @@ asciidoc:
|
||||
spring-framework-reference-url: $springFrameworkReferenceUrl
|
||||
spring-security-version: ${project.version}
|
||||
${ymlVersions}
|
||||
""")
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
dependencies {
|
||||
testImplementation platform(project(":spring-security-dependencies"))
|
||||
testImplementation "com.unboundid:unboundid-ldapsdk"
|
||||
testImplementation "org.apache.directory.server:apacheds-core"
|
||||
testImplementation "org.springframework:spring-core"
|
||||
testImplementation platform(project(':spring-security-dependencies'))
|
||||
testImplementation 'com.unboundid:unboundid-ldapsdk'
|
||||
testImplementation 'org.apache.directory.server:apacheds-core'
|
||||
testImplementation 'org.springframework:spring-core'
|
||||
}
|
||||
|
||||
def resolvedVersions(Configuration configuration) {
|
||||
return {
|
||||
configuration.resolvedConfiguration
|
||||
.resolvedArtifacts
|
||||
.collectEntries { [(it.name + "-version"): it.moduleVersion.id.version] }
|
||||
.collectEntries { [(it.name + '-version'): it.moduleVersion.id.version] }
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url "https://repo.spring.io/release" }
|
||||
maven { url "https://repo.spring.io/milestone" }
|
||||
maven { url "https://repo.spring.io/snapshot" }
|
||||
maven { url 'https://repo.spring.io/release' }
|
||||
maven { url 'https://repo.spring.io/milestone' }
|
||||
maven { url 'https://repo.spring.io/snapshot' }
|
||||
}
|
||||
|
||||
Executable
+135
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/ruby
|
||||
require 'json'
|
||||
require 'net/http'
|
||||
require 'yaml'
|
||||
require 'logger'
|
||||
|
||||
$log = Logger.new(STDOUT)
|
||||
$log.level = Logger::WARN
|
||||
|
||||
class ForwardMerge
|
||||
attr_reader :issue, :milestone, :message, :line
|
||||
def initialize(issue, milestone, message, line)
|
||||
@issue = issue
|
||||
@milestone = milestone
|
||||
@message = message
|
||||
@line = line
|
||||
end
|
||||
end
|
||||
|
||||
def find_forward_merges(message_file)
|
||||
$log.debug "Searching for forward merge"
|
||||
rev=`git rev-parse -q --verify MERGE_HEAD`.strip
|
||||
$log.debug "Found #{rev} from git rev-parse"
|
||||
return nil unless rev
|
||||
message = File.read(message_file)
|
||||
forward_merges = []
|
||||
message.each_line do |line|
|
||||
$log.debug "Checking #{line} for message"
|
||||
match = /^(?:Fixes|Closes) gh-(\d+) in (\d\.\d\.[\dx](?:[\.\-](?:M|RC)\d)?)$/.match(line)
|
||||
if match then
|
||||
issue = match[1]
|
||||
milestone = match[2]
|
||||
$log.debug "Matched reference to issue #{issue} in milestone #{milestone}"
|
||||
forward_merges << ForwardMerge.new(issue, milestone, message, line)
|
||||
end
|
||||
end
|
||||
$log.debug "No match in merge message" unless forward_merges
|
||||
return forward_merges
|
||||
end
|
||||
|
||||
def get_issue(username, password, repository, number)
|
||||
$log.debug "Getting issue #{number} from GitHub repository #{repository}"
|
||||
uri = URI("https://api.github.com/repos/#{repository}/issues/#{number}")
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
http.use_ssl=true
|
||||
request = Net::HTTP::Get.new(uri.path)
|
||||
request.basic_auth(username, password)
|
||||
response = http.request(request)
|
||||
$log.debug "Get HTTP response #{response.code}"
|
||||
return JSON.parse(response.body) unless response.code != '200'
|
||||
puts "Failed to retrieve issue #{number}: #{response.message}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
def find_milestone(username, password, repository, title)
|
||||
$log.debug "Finding milestone #{title} from GitHub repository #{repository}"
|
||||
uri = URI("https://api.github.com/repos/#{repository}/milestones")
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
http.use_ssl=true
|
||||
request = Net::HTTP::Get.new(uri.path)
|
||||
request.basic_auth(username, password)
|
||||
response = http.request(request)
|
||||
milestones = JSON.parse(response.body)
|
||||
if title.end_with?(".x")
|
||||
prefix = title.delete_suffix('.x')
|
||||
$log.debug "Finding nearest milestone from candidates starting with #{prefix}"
|
||||
titles = milestones.map { |milestone| milestone['title'] }
|
||||
titles = titles.select{ |title| title.start_with?(prefix) unless title.end_with?('.x')}
|
||||
titles = titles.sort_by { |v| Gem::Version.new(v) }
|
||||
$log.debug "Considering candidates #{titles}"
|
||||
if(titles.empty?)
|
||||
puts "Cannot find nearest milestone for prefix #{title}"
|
||||
exit 1
|
||||
end
|
||||
title = titles.first
|
||||
$log.debug "Found nearest milestone #{title}"
|
||||
end
|
||||
milestones.each do |milestone|
|
||||
$log.debug "Considering #{milestone['title']}"
|
||||
return milestone['number'] if milestone['title'] == title
|
||||
end
|
||||
puts "Milestone #{title} not found in #{repository}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
def create_issue(username, password, repository, original, title, labels, milestone, milestone_name, dry_run)
|
||||
$log.debug "Finding forward-merge issue in GitHub repository #{repository} for '#{title}'"
|
||||
uri = URI("https://api.github.com/repos/#{repository}/issues")
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
http.use_ssl=true
|
||||
request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
|
||||
request.basic_auth(username, password)
|
||||
request.body = {
|
||||
title: title,
|
||||
labels: labels,
|
||||
milestone: milestone.to_i,
|
||||
body: "Forward port of issue ##{original} to #{milestone_name}."
|
||||
}.to_json
|
||||
if dry_run then
|
||||
puts "Dry run"
|
||||
puts "POSTing to #{uri} with body #{request.body}"
|
||||
return "dry-run"
|
||||
end
|
||||
response = JSON.parse(http.request(request).body)
|
||||
$log.debug "Created new issue #{response['number']}"
|
||||
return response['number']
|
||||
end
|
||||
|
||||
$log.debug "Running forward-merge hook script"
|
||||
message_file=ARGV[0]
|
||||
|
||||
forward_merges = find_forward_merges(message_file)
|
||||
exit 0 unless forward_merges
|
||||
|
||||
$log.debug "Loading config from ~/.spring-boot/forward_merge.yml"
|
||||
config = YAML.load_file(File.join(Dir.home, '.spring-boot', 'forward-merge.yml'))
|
||||
username = config['github']['credentials']['username']
|
||||
password = config['github']['credentials']['password']
|
||||
dry_run = config['dry_run']
|
||||
repository = 'spring-projects/spring-security'
|
||||
|
||||
forward_merges.each do |forward_merge|
|
||||
existing_issue = get_issue(username, password, repository, forward_merge.issue)
|
||||
title = existing_issue['title']
|
||||
labels = existing_issue['labels'].map { |label| label['name'] }
|
||||
labels << "status: forward-port"
|
||||
$log.debug "Processing issue '#{title}'"
|
||||
|
||||
milestone = find_milestone(username, password, repository, forward_merge.milestone)
|
||||
new_issue_number = create_issue(username, password, repository, forward_merge.issue, title, labels, milestone, forward_merge.milestone, dry_run)
|
||||
|
||||
puts "Created gh-#{new_issue_number} for forward port of gh-#{forward_merge.issue} into #{forward_merge.milestone}"
|
||||
rewritten_message = forward_merge.message.sub(forward_merge.line, "Closes gh-#{new_issue_number}\n")
|
||||
File.write(message_file, rewritten_message)
|
||||
end
|
||||
Executable
+71
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/ruby
|
||||
require 'json'
|
||||
require 'net/http'
|
||||
require 'yaml'
|
||||
require 'logger'
|
||||
|
||||
$main_branch = "6.0.x"
|
||||
|
||||
$log = Logger.new(STDOUT)
|
||||
$log.level = Logger::WARN
|
||||
|
||||
def get_fixed_issues()
|
||||
$log.debug "Searching for for forward merge"
|
||||
rev=`git rev-parse -q --verify MERGE_HEAD`.strip
|
||||
$log.debug "Found #{rev} from git rev-parse"
|
||||
return nil unless rev
|
||||
fixed = []
|
||||
message = `git log -1 --pretty=%B #{rev}`
|
||||
message.each_line do |line|
|
||||
$log.debug "Checking #{line} for message"
|
||||
fixed << line.strip if /^(?:Fixes|Closes) gh-(\d+)/.match(line)
|
||||
end
|
||||
$log.debug "Found fixed issues #{fixed}"
|
||||
return fixed;
|
||||
end
|
||||
|
||||
def rewrite_message(message_file, fixed)
|
||||
current_branch = `git rev-parse --abbrev-ref HEAD`.strip
|
||||
if current_branch == "main"
|
||||
current_branch = $main_branch
|
||||
end
|
||||
rewritten_message = ""
|
||||
message = File.read(message_file)
|
||||
message.each_line do |line|
|
||||
match = /^Merge.*branch\ '(.*)'(?:\ into\ (.*))?$/.match(line)
|
||||
if match
|
||||
from_branch = match[1]
|
||||
if from_branch.include? "/"
|
||||
from_branch = from_branch.partition("/").last
|
||||
end
|
||||
to_brach = match[2]
|
||||
$log.debug "Rewriting merge message"
|
||||
line = "Merge branch '#{from_branch}'" + (to_brach ? " into #{to_brach}\n" : "\n")
|
||||
end
|
||||
if fixed and line.start_with?("#")
|
||||
$log.debug "Adding fixed"
|
||||
rewritten_message << "\n"
|
||||
fixed.each do |fixes|
|
||||
rewritten_message << "#{fixes} in #{current_branch}\n"
|
||||
end
|
||||
fixed = nil
|
||||
end
|
||||
rewritten_message << line
|
||||
end
|
||||
return rewritten_message
|
||||
end
|
||||
|
||||
$log.debug "Running prepare-forward-merge hook script"
|
||||
|
||||
message_file=ARGV[0]
|
||||
message_type=ARGV[1]
|
||||
|
||||
if message_type != "merge"
|
||||
$log.debug "Not a merge commit"
|
||||
exit 0;
|
||||
end
|
||||
|
||||
$log.debug "Searching for for forward merge"
|
||||
fixed = get_fixed_issues()
|
||||
rewritten_message = rewrite_message(message_file, fixed)
|
||||
File.write(message_file, rewritten_message)
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
aspectjVersion=1.9.9.1
|
||||
springJavaformatVersion=0.0.31
|
||||
springBootVersion=2.4.2
|
||||
springFrameworkVersion=5.3.22
|
||||
springFrameworkVersion=5.3.23
|
||||
openSamlVersion=3.4.6
|
||||
version=5.6.7
|
||||
version=5.6.8
|
||||
kotlinVersion=1.5.32
|
||||
samplesBranch=5.6.x
|
||||
org.gradle.jvmargs=-Xmx3g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
site:
|
||||
title: Spring Security
|
||||
start_page: security::index.adoc
|
||||
asciidoc:
|
||||
attributes:
|
||||
page-pagination: true
|
||||
content:
|
||||
sources:
|
||||
- url: ./
|
||||
branches: [HEAD]
|
||||
start_path: docs
|
||||
- url: ../../rwinch/spring-security-docs-generated
|
||||
branches: [HEAD]
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/rwinch/antora-ui-spring/releases/download/latest/ui-bundle.zip
|
||||
snapshot: true
|
||||
+34
-38
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -338,8 +338,8 @@ public final class NimbusJwtDecoder implements JwtDecoder {
|
||||
if (this.cache == null) {
|
||||
return new RemoteJWKSet<>(toURL(this.jwkSetUri), jwkSetRetriever);
|
||||
}
|
||||
ResourceRetriever cachingJwkSetRetriever = new CachingResourceRetriever(this.cache, jwkSetRetriever);
|
||||
return new RemoteJWKSet<>(toURL(this.jwkSetUri), cachingJwkSetRetriever, new NoOpJwkSetCache());
|
||||
JWKSetCache jwkSetCache = new SpringJWKSetCache(this.jwkSetUri, this.cache);
|
||||
return new RemoteJWKSet<>(toURL(this.jwkSetUri), jwkSetRetriever, jwkSetCache);
|
||||
}
|
||||
|
||||
JWTProcessor<SecurityContext> processor() {
|
||||
@@ -371,52 +371,48 @@ public final class NimbusJwtDecoder implements JwtDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
private static class NoOpJwkSetCache implements JWKSetCache {
|
||||
private static final class SpringJWKSetCache implements JWKSetCache {
|
||||
|
||||
private final String jwkSetUri;
|
||||
|
||||
private final Cache cache;
|
||||
|
||||
private JWKSet jwkSet;
|
||||
|
||||
SpringJWKSetCache(String jwkSetUri, Cache cache) {
|
||||
this.jwkSetUri = jwkSetUri;
|
||||
this.cache = cache;
|
||||
this.updateJwkSetFromCache();
|
||||
}
|
||||
|
||||
private void updateJwkSetFromCache() {
|
||||
String cachedJwkSet = this.cache.get(this.jwkSetUri, String.class);
|
||||
if (cachedJwkSet != null) {
|
||||
try {
|
||||
this.jwkSet = JWKSet.parse(cachedJwkSet);
|
||||
}
|
||||
catch (ParseException ignored) {
|
||||
// Ignore invalid cache value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: Only called from inside a synchronized block in RemoteJWKSet.
|
||||
@Override
|
||||
public void put(JWKSet jwkSet) {
|
||||
this.jwkSet = jwkSet;
|
||||
this.cache.put(this.jwkSetUri, jwkSet.toString(false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public JWKSet get() {
|
||||
return null;
|
||||
return (!requiresRefresh()) ? this.jwkSet : null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresRefresh() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class CachingResourceRetriever implements ResourceRetriever {
|
||||
|
||||
private final Cache cache;
|
||||
|
||||
private final ResourceRetriever resourceRetriever;
|
||||
|
||||
CachingResourceRetriever(Cache cache, ResourceRetriever resourceRetriever) {
|
||||
this.cache = cache;
|
||||
this.resourceRetriever = resourceRetriever;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource retrieveResource(URL url) throws IOException {
|
||||
try {
|
||||
String jwkSet = this.cache.get(url.toString(),
|
||||
() -> this.resourceRetriever.retrieveResource(url).getContent());
|
||||
return new Resource(jwkSet, "UTF-8");
|
||||
}
|
||||
catch (Cache.ValueRetrievalException ex) {
|
||||
Throwable thrownByValueLoader = ex.getCause();
|
||||
if (thrownByValueLoader instanceof IOException) {
|
||||
throw (IOException) thrownByValueLoader;
|
||||
}
|
||||
throw new IOException(thrownByValueLoader);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IOException(ex);
|
||||
}
|
||||
return this.cache.get(this.jwkSetUri) == null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+113
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -32,10 +32,10 @@ import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import com.nimbusds.jose.JOSEException;
|
||||
import com.nimbusds.jose.JOSEObjectType;
|
||||
import com.nimbusds.jose.JWSAlgorithm;
|
||||
import com.nimbusds.jose.JWSHeader;
|
||||
@@ -82,6 +82,7 @@ import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
@@ -97,10 +98,14 @@ public class NimbusJwtDecoderTests {
|
||||
|
||||
private static final String JWK_SET = "{\"keys\":[{\"p\":\"49neceJFs8R6n7WamRGy45F5Tv0YM-R2ODK3eSBUSLOSH2tAqjEVKOkLE5fiNA3ygqq15NcKRadB2pTVf-Yb5ZIBuKzko8bzYIkIqYhSh_FAdEEr0vHF5fq_yWSvc6swsOJGqvBEtuqtJY027u-G2gAQasCQdhyejer68zsTn8M\",\"kty\":\"RSA\",\"q\":\"tWR-ysspjZ73B6p2vVRVyHwP3KQWL5KEQcdgcmMOE_P_cPs98vZJfLhxobXVmvzuEWBpRSiqiuyKlQnpstKt94Cy77iO8m8ISfF3C9VyLWXi9HUGAJb99irWABFl3sNDff5K2ODQ8CmuXLYM25OwN3ikbrhEJozlXg_NJFSGD4E\",\"d\":\"FkZHYZlw5KSoqQ1i2RA2kCUygSUOf1OqMt3uomtXuUmqKBm_bY7PCOhmwbvbn4xZYEeHuTR8Xix-0KpHe3NKyWrtRjkq1T_un49_1LLVUhJ0dL-9_x0xRquVjhl_XrsRXaGMEHs8G9pLTvXQ1uST585gxIfmCe0sxPZLvwoic-bXf64UZ9BGRV3lFexWJQqCZp2S21HfoU7wiz6kfLRNi-K4xiVNB1gswm_8o5lRuY7zB9bRARQ3TS2G4eW7p5sxT3CgsGiQD3_wPugU8iDplqAjgJ5ofNJXZezoj0t6JMB_qOpbrmAM1EnomIPebSLW7Ky9SugEd6KMdL5lW6AuAQ\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"qi\":\"wdkFu_tV2V1l_PWUUimG516Zvhqk2SWDw1F7uNDD-Lvrv_WNRIJVzuffZ8WYiPy8VvYQPJUrT2EXL8P0ocqwlaSTuXctrORcbjwgxDQDLsiZE0C23HYzgi0cofbScsJdhcBg7d07LAf7cdJWG0YVl1FkMCsxUlZ2wTwHfKWf-v4\",\"dp\":\"uwnPxqC-IxG4r33-SIT02kZC1IqC4aY7PWq0nePiDEQMQWpjjNH50rlq9EyLzbtdRdIouo-jyQXB01K15-XXJJ60dwrGLYNVqfsTd0eGqD1scYJGHUWG9IDgCsxyEnuG3s0AwbW2UolWVSsU2xMZGb9PurIUZECeD1XDZwMp2s0\",\"dq\":\"hra786AunB8TF35h8PpROzPoE9VJJMuLrc6Esm8eZXMwopf0yhxfN2FEAvUoTpLJu93-UH6DKenCgi16gnQ0_zt1qNNIVoRfg4rw_rjmsxCYHTVL3-RDeC8X_7TsEySxW0EgFTHh-nr6I6CQrAJjPM88T35KHtdFATZ7BCBB8AE\",\"n\":\"oXJ8OyOv_eRnce4akdanR4KYRfnC2zLV4uYNQpcFn6oHL0dj7D6kxQmsXoYgJV8ZVDn71KGmuLvolxsDncc2UrhyMBY6DVQVgMSVYaPCTgW76iYEKGgzTEw5IBRQL9w3SRJWd3VJTZZQjkXef48Ocz06PGF3lhbz4t5UEZtdF4rIe7u-977QwHuh7yRPBQ3sII-cVoOUMgaXB9SHcGF2iZCtPzL_IffDUcfhLQteGebhW8A6eUHgpD5A1PQ-JCw_G7UOzZAjjDjtNM2eqm8j-Ms_gqnm4MiCZ4E-9pDN77CAAPVN7kuX6ejs9KBXpk01z48i9fORYk9u7rAkh1HuQw\"}]}";
|
||||
|
||||
private static final String NEW_KID_JWK_SET = "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"kid\":\"two\",\"n\":\"ra9UJw4I0fCHuOqr1xWJsh-qcVeZWtKEU3uoqq1sAg5fG67dujNCm_Q16yuO0ZdDiU0vlJkbc_MXFAvm4ZxdJ_qR7PAneV-BOGNtLpSaiPclscCy3m7zjRWkaqwt9ZZEsdK5UqXyPlBpcYhNKsmnQGjnX4sYb7d8b2jSCM_qto48-6451rbyEhXXywtFy_JqtTpbsw_IIdQHMr1O-MdSjsQxX9kkvZwPU8LsC-CcqlcsZ7mnpOhmIXaf4tbRwAaluXwYft0yykFsp8e5C4t9mMs9Vu8AB5gT8o-D_ovXd2qh4k3ejzVpYLtzD4nbfvPJA_TXmjhn-9GOPAqkzfON2Q\"}]}";
|
||||
|
||||
private static final String MALFORMED_JWK_SET = "malformed";
|
||||
|
||||
private static final String SIGNED_JWT = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJzY3AiOlsibWVzc2FnZTpyZWFkIl0sImV4cCI6NDY4Mzg5Nzc3Nn0.LtMVtIiRIwSyc3aX35Zl0JVwLTcQZAB3dyBOMHNaHCKUljwMrf20a_gT79LfhjDzE_fUVUmFiAO32W1vFnYpZSVaMDUgeIOIOpxfoe9shj_uYenAwIS-_UxqGVIJiJoXNZh_MK80ShNpvsQwamxWEEOAMBtpWNiVYNDMdfgho9n3o5_Z7Gjy8RLBo1tbDREbO9kTFwGIxm_EYpezmRCRq4w1DdS6UDW321hkwMxPnCMSWOvp-hRpmgY2yjzLgPJ6Aucmg9TJ8jloAP1DjJoF1gRR7NTAk8LOGkSjTzVYDYMbCF51YdpojhItSk80YzXiEsv1mTz4oMM49jXBmfXFMA";
|
||||
|
||||
private static final String NEW_KID_SIGNED_JWT = "eyJraWQiOiJ0d28iLCJhbGciOiJSUzI1NiJ9.eyJleHAiOjIxMzMyNzg4MjV9.DQJn_qg0HfZ_sjlx9MJkdCjkp9t-0zOj3FzVp_UPzx6RCcBb8Jk373dNgcyfOP5CS29wv5gKX6geWEDj5cgqcJdTS53zqOaLETdNnKACd056SkPqgTLJv12gdJx7tr5WbBqRB9Y0ce96vbH6wwQGfqU_1Lz1RhZ7ZZuvIuWLp75ujld7dOshScg728Z9BQsiFOH_yFp09XraO15spwTXp9RO5TJRUSLih-5V3sdxHa5rPTm6by7me8I_l4iMJN81Z95_O7sbLeYH-4zZ-3T49uPyAC5suEOd-P5aFP89zPKh9Y3Uviu2OyvpUuXmpUjTtdAKf3p96dOEeLJvT3hkSg";
|
||||
|
||||
private static final String MALFORMED_JWT = "eyJhbGciOiJSUzI1NiJ9.eyJuYmYiOnt9LCJleHAiOjQ2ODQyMjUwODd9.guoQvujdWvd3xw7FYQEn4D6-gzM_WqFvXdmvAUNSLbxG7fv2_LLCNujPdrBHJoYPbOwS1BGNxIKQWS1tylvqzmr1RohQ-RZ2iAM1HYQzboUlkoMkcd8ENM__ELqho8aNYBfqwkNdUOyBFoy7Syu_w2SoJADw2RTjnesKO6CVVa05bW118pDS4xWxqC4s7fnBjmZoTn4uQ-Kt9YSQZQk8YQxkJSiyanozzgyfgXULA6mPu1pTNU3FVFaK1i1av_xtH_zAPgb647ZeaNe4nahgqC5h8nhOlm8W2dndXbwAt29nd2ZWBsru_QwZz83XSKLhTPFz-mPBByZZDsyBbIHf9A";
|
||||
|
||||
private static final String UNSIGNED_JWT = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOi0yMDMzMjI0OTcsImp0aSI6IjEyMyIsInR5cCI6IkpXVCJ9.";
|
||||
@@ -644,10 +649,38 @@ public class NimbusJwtDecoderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeWhenCacheThenRetrieveFromCache() {
|
||||
public void decodeWhenCacheStoredThenAbleToRetrieveJwkSetFromCache() {
|
||||
Cache cache = new ConcurrentMapCache("test-jwk-set-cache");
|
||||
RestOperations restOperations = mock(RestOperations.class);
|
||||
given(restOperations.exchange(any(RequestEntity.class), eq(String.class)))
|
||||
.willReturn(new ResponseEntity<>(JWK_SET, HttpStatus.OK));
|
||||
// @formatter:off
|
||||
NimbusJwtDecoder jwtDecoder1 = NimbusJwtDecoder.withJwkSetUri(JWK_SET_URI)
|
||||
.restOperations(restOperations)
|
||||
.cache(cache)
|
||||
.build();
|
||||
// @formatter:on
|
||||
jwtDecoder1.decode(SIGNED_JWT);
|
||||
assertThat(cache.get(JWK_SET_URI, String.class)).isEqualTo(JWK_SET);
|
||||
verify(restOperations).exchange(any(RequestEntity.class), eq(String.class));
|
||||
|
||||
// @formatter:off
|
||||
NimbusJwtDecoder jwtDecoder2 = NimbusJwtDecoder.withJwkSetUri(JWK_SET_URI)
|
||||
.restOperations(restOperations)
|
||||
.cache(cache)
|
||||
.build();
|
||||
// @formatter:on
|
||||
jwtDecoder2.decode(SIGNED_JWT);
|
||||
verifyNoMoreInteractions(restOperations);
|
||||
}
|
||||
|
||||
// gh-11621
|
||||
@Test
|
||||
public void decodeWhenCacheThenRetrieveFromCache() throws Exception {
|
||||
RestOperations restOperations = mock(RestOperations.class);
|
||||
Cache cache = mock(Cache.class);
|
||||
given(cache.get(eq(JWK_SET_URI), any(Callable.class))).willReturn(JWK_SET);
|
||||
given(cache.get(eq(JWK_SET_URI), eq(String.class))).willReturn(JWK_SET);
|
||||
given(cache.get(eq(JWK_SET_URI))).willReturn(mock(Cache.ValueWrapper.class));
|
||||
// @formatter:off
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(JWK_SET_URI)
|
||||
.cache(cache)
|
||||
@@ -655,11 +688,65 @@ public class NimbusJwtDecoderTests {
|
||||
.build();
|
||||
// @formatter:on
|
||||
jwtDecoder.decode(SIGNED_JWT);
|
||||
verify(cache).get(eq(JWK_SET_URI), any(Callable.class));
|
||||
verify(cache).get(eq(JWK_SET_URI), eq(String.class));
|
||||
verify(cache, times(2)).get(eq(JWK_SET_URI));
|
||||
verifyNoMoreInteractions(cache);
|
||||
verifyNoInteractions(restOperations);
|
||||
}
|
||||
|
||||
// gh-11621
|
||||
@Test
|
||||
public void decodeWhenCacheAndUnknownKidShouldTriggerFetchOfJwkSet() throws JOSEException {
|
||||
RestOperations restOperations = mock(RestOperations.class);
|
||||
Cache cache = mock(Cache.class);
|
||||
given(cache.get(eq(JWK_SET_URI), eq(String.class))).willReturn(JWK_SET);
|
||||
given(restOperations.exchange(any(RequestEntity.class), eq(String.class)))
|
||||
.willReturn(new ResponseEntity<>(NEW_KID_JWK_SET, HttpStatus.OK));
|
||||
|
||||
// @formatter:off
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(JWK_SET_URI)
|
||||
.cache(cache)
|
||||
.restOperations(restOperations)
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
// Decode JWT with new KID
|
||||
jwtDecoder.decode(NEW_KID_SIGNED_JWT);
|
||||
|
||||
ArgumentCaptor<RequestEntity> requestEntityCaptor = ArgumentCaptor.forClass(RequestEntity.class);
|
||||
verify(restOperations).exchange(requestEntityCaptor.capture(), eq(String.class));
|
||||
verifyNoMoreInteractions(restOperations);
|
||||
assertThat(requestEntityCaptor.getValue().getHeaders().getAccept()).contains(MediaType.APPLICATION_JSON,
|
||||
APPLICATION_JWK_SET_JSON);
|
||||
}
|
||||
|
||||
// gh-11621
|
||||
@Test
|
||||
public void decodeWithoutCacheSpecifiedAndUnknownKidShouldTriggerFetchOfJwkSet() throws JOSEException {
|
||||
RestOperations restOperations = mock(RestOperations.class);
|
||||
given(restOperations.exchange(any(RequestEntity.class), eq(String.class))).willReturn(
|
||||
new ResponseEntity<>(JWK_SET, HttpStatus.OK), new ResponseEntity<>(NEW_KID_JWK_SET, HttpStatus.OK));
|
||||
|
||||
// @formatter:off
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(JWK_SET_URI)
|
||||
.restOperations(restOperations)
|
||||
.build();
|
||||
// @formatter:on
|
||||
jwtDecoder.decode(SIGNED_JWT);
|
||||
|
||||
// Decode JWT with new KID
|
||||
jwtDecoder.decode(NEW_KID_SIGNED_JWT);
|
||||
|
||||
ArgumentCaptor<RequestEntity> requestEntityCaptor = ArgumentCaptor.forClass(RequestEntity.class);
|
||||
verify(restOperations, times(2)).exchange(requestEntityCaptor.capture(), eq(String.class));
|
||||
verifyNoMoreInteractions(restOperations);
|
||||
List<RequestEntity> requestEntities = requestEntityCaptor.getAllValues();
|
||||
assertThat(requestEntities.get(0).getHeaders().getAccept()).contains(MediaType.APPLICATION_JSON,
|
||||
APPLICATION_JWK_SET_JSON);
|
||||
assertThat(requestEntities.get(1).getHeaders().getAccept()).contains(MediaType.APPLICATION_JSON,
|
||||
APPLICATION_JWK_SET_JSON);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeWhenCacheIsConfiguredAndValueLoaderErrorsThenThrowsJwtException() {
|
||||
Cache cache = new ConcurrentMapCache("test-jwk-set-cache");
|
||||
@@ -678,6 +765,27 @@ public class NimbusJwtDecoderTests {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
// gh-11621
|
||||
@Test
|
||||
public void decodeWhenCacheIsConfiguredAndParseFailsOnCachedValueThenExceptionIgnored() {
|
||||
RestOperations restOperations = mock(RestOperations.class);
|
||||
Cache cache = mock(Cache.class);
|
||||
given(cache.get(eq(JWK_SET_URI), eq(String.class))).willReturn(JWK_SET);
|
||||
given(cache.get(eq(JWK_SET_URI))).willReturn(mock(Cache.ValueWrapper.class));
|
||||
// @formatter:off
|
||||
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withJwkSetUri(JWK_SET_URI)
|
||||
.cache(cache)
|
||||
.restOperations(restOperations)
|
||||
.build();
|
||||
// @formatter:on
|
||||
jwtDecoder.decode(SIGNED_JWT);
|
||||
verify(cache).get(eq(JWK_SET_URI), eq(String.class));
|
||||
verify(cache, times(2)).get(eq(JWK_SET_URI));
|
||||
verifyNoMoreInteractions(cache);
|
||||
verifyNoInteractions(restOperations);
|
||||
|
||||
}
|
||||
|
||||
// gh-8730
|
||||
@Test
|
||||
public void withJwkSetUriWhenUsingCustomTypeHeaderThenRefuseOmittedType() throws Exception {
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ pluginManagement {
|
||||
}
|
||||
|
||||
plugins {
|
||||
id "com.gradle.enterprise" version "3.6.1"
|
||||
id "com.gradle.enterprise" version "3.11.1"
|
||||
id "io.spring.ge.conventions" version "0.0.7"
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user