1
0
mirror of synced 2026-07-11 05:40:02 +00:00

Compare commits

...

31 Commits

Author SHA1 Message Date
Josh Cummings 89c815032c Fix Index Out of Bounds 2022-10-17 14:52:03 -06:00
Josh Cummings ff055cf07a Remote antoraUpdateVersion task from CI 2022-10-17 14:24:13 -06:00
github-actions[bot] b08a06aa76 Release 5.6.8 2022-10-17 18:55:50 +00:00
Marcus Da Coregio 465d80c162 Remove antoreUpdateVersion task from release automation 2022-10-17 15:50:44 -03:00
Marcus Da Coregio 4992e8ce62 Update org.springframework.data to 2021.1.8
Closes gh-12007
2022-10-13 09:24:21 -03:00
Marcus Da Coregio c772daab92 Update org.springframework to 5.3.23
Closes gh-12006
2022-10-13 09:24:20 -03:00
Marcus Da Coregio 45a4a89960 Update hibernate-entitymanager to 5.6.12.Final
Closes gh-12005
2022-10-13 09:24:20 -03:00
Marcus Da Coregio b43c7e927f Update org.eclipse.jetty to 9.4.49.v20220914
Closes gh-12004
2022-10-13 09:24:20 -03:00
Marcus Da Coregio 50d23622d0 Update io.rsocket to 1.1.3
Closes gh-12003
2022-10-13 09:24:20 -03:00
Marcus Da Coregio 2c2603ba0f Update io.projectreactor to 2020.0.24
Closes gh-12001
2022-10-13 09:24:20 -03:00
Marcus Da Coregio f7f53ea2b7 Update jackson-bom to 2.13.4.20221012
Closes gh-11997
2022-10-13 09:22:28 -03:00
Marcus Da Coregio e0f8c711d8 Add default value for cloneOutputDirectory property
Closes gh-11969
2022-10-10 11:00:47 -03:00
Dan Allen e0843aabb1 automatically manage docs version (with collector) 2022-10-05 13:56:22 -05:00
Daniel Garnier-Moiroux 26bb60c567 Add rncToXsd task description to CONTRIBUTING.adoc 2022-10-03 10:09:27 -03:00
Dan Allen c44230ba24 switch to offical Antora plugin for Gradle
- lock version to latest release of Antora 3.1
- rename properties on extension block
- use Node.js version provided by plugin
- remove package.json file
- assign environment variables using environments property on extension block
- use single quotes where possible in build script
- use default setting for log format
2022-09-29 14:05:09 -05:00
Jerome Prinet 8d2fb6858f Update Gradle Enterprise plugin to 3.11.1 2022-09-16 13:14:53 -03:00
Marcus Da Coregio 7756247c3a Simplify checkSamples task
Closes gh-11814
2022-09-16 09:36:12 -03:00
Dan Allen 3387149a0f repurpose 5.6.x branch to provide local docs build
* remove unused workflows, scripts, and configuration (now handled by docs-build branch)
* upgrade Antora to 3.1 (and Node.js to 16)
* tune playbook settings
* reconfigure docs build for local build only
* add patch to support using linked worktree as Antora content source
* remove Antora extensions not needed for local builds
2022-09-12 15:41:12 -05:00
Underground Hill 8b74bf9742 Updated reference to architecture page
In the context of Servlet Authentication page, "Architecture" should probably link to "Servlet Authentication Architecture" page
2022-09-01 09:38:10 -03:00
Marcus Da Coregio 40abf87ae6 Add buildScan to checkRemote
Closes gh-11766
2022-08-30 09:11:08 -03:00
Steve Riesenberg 1c014eb512 Use 6.0.x instead of 3.0.x as default branch 2022-08-24 16:38:27 -05:00
Rob Winch 8c69699458 Remove backportbot.yml
Issue gh-11736
2022-08-23 13:46:32 -05:00
Rob Winch fc10d5fc29 repository=spring-projects/spring-security
Previously the repository used spring-project (missing the s)
2022-08-23 13:30:20 -05:00
Rob Winch c79ebf4edf Setup Forward Merge 2022-08-22 16:19:44 -05:00
Marcus Da Coregio a8d6c1d21f Consistently set AuthenticationEventPublisher in AuthenticationManagerBuilder
Prior to this, the HttpSecurity bean was not consistent with WebSecurityConfigurerAdapter's HttpSecurity because it did not setup a default AuthenticationEventPublisher. This also fixes a problem where the AuthenticationEventPublisher bean would only be considered if there was a UserDetailsService

Closes gh-11449
Closes gh-11726
2022-08-19 09:58:22 -03:00
Steve Riesenberg 7c7f9380c7 Refresh remote JWK when unknown KID error occurs
Closes gh-11621
2022-08-18 16:54:45 -05:00
tinolazreg 888715bbb2 Add tests for unknown KID error
Issue gh-11621
2022-08-18 16:54:45 -05:00
jujunChen 13feb87171 Modify words
- <dependencyManagement> to dependencyManagement
- pom.xml to build.gradle
2022-08-16 14:51:36 -06:00
Rob Winch faf9fb7337 NamespaceLdapAuthenticationProviderTests use Dynamic Port
Closes gh-11710
2022-08-15 15:26:46 -05:00
Rob Winch f33d7253b6 GitHubMilestoneApiTests due_on Uses LocalDate
`GitHubMilestoneApiTests` uses `Instant.now()` for `due_on`. Since
`Instant.now()` is UTC time based,
`isMilestoneDueTodayWhenDueTodayThenTrue` fails when the computer that runs
the test is not the same day as it is in UTC time.

To fix it, `due_on` should be set to an `Instant` based upon the timezone
of the current computer.

Closes gh-11706
2022-08-15 13:04:29 -05:00
github-actions[bot] db74e9d128 Next development version 2022-08-15 16:07:33 +00:00
39 changed files with 684 additions and 799 deletions
-20
View File
@@ -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"
}
}
-20
View File
@@ -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
-20
View File
@@ -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"
-33
View File
@@ -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:
-34
View File
@@ -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
View File
@@ -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
View File
@@ -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 {
+1 -1
View File
@@ -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 +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();
}
}
@@ -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" +
"]";
@@ -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
@@ -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();
@@ -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);
@@ -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();
@@ -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
View File
@@ -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"
+2
View File
@@ -0,0 +1,2 @@
/package-lock.json
/node_modules/
+53
View File
@@ -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))
}
}
})
}
-26
View File
@@ -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
View File
@@ -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('/') || '.'
}
}
+14 -16
View File
@@ -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.
+41 -40
View File
@@ -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' }
}
+135
View File
@@ -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
+71
View File
@@ -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
View File
@@ -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
-17
View File
@@ -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
@@ -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;
}
}
@@ -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
View File
@@ -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"
}