1
0
mirror of synced 2026-09-08 18:29:49 +00:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Josh Cummings e3e7d76b70 Add Workflow to Finalize a Release 2025-11-04 09:32:41 -07:00
1064 changed files with 4315 additions and 21096 deletions
+20 -99
View File
@@ -1,10 +1,12 @@
version: 2 version: 2
registries: registries:
spring-milestones:
type: maven-repository
url: https://repo.spring.io/milestone
shibboleth: shibboleth:
type: maven-repository type: maven-repository
url: https://build.shibboleth.net/maven/releases url: https://build.shibboleth.net/maven/releases
updates: updates:
# 6.5.x
- package-ecosystem: gradle - package-ecosystem: gradle
target-branch: 6.5.x target-branch: 6.5.x
directory: / directory: /
@@ -15,6 +17,7 @@ updates:
labels: labels:
- 'type: dependency-upgrade' - 'type: dependency-upgrade'
registries: registries:
- spring-milestones
- shibboleth - shibboleth
ignore: ignore:
- dependency-name: com.nimbusds:nimbus-jose-jwt - dependency-name: com.nimbusds:nimbus-jose-jwt
@@ -31,28 +34,8 @@ updates:
update-types: update-types:
- version-update:semver-major - version-update:semver-major
- version-update:semver-minor - version-update:semver-minor
- package-ecosystem: npm
target-branch: 6.5.x
directory: /docs
schedule:
interval: weekly
labels:
- 'type: task'
- 'type: dependency-upgrade'
- 'in: build'
- package-ecosystem: github-actions
target-branch: 6.5.x
directory: /
schedule:
interval: weekly
labels:
- 'type: task'
- 'type: dependency-upgrade'
- 'in: build'
# 7.0.x
- package-ecosystem: gradle - package-ecosystem: gradle
target-branch: 7.0.x target-branch: 6.4.x
directory: / directory: /
schedule: schedule:
interval: daily interval: daily
@@ -61,10 +44,10 @@ updates:
labels: labels:
- 'type: dependency-upgrade' - 'type: dependency-upgrade'
registries: registries:
- spring-milestones
- shibboleth - shibboleth
ignore: ignore:
- dependency-name: com.nimbusds:nimbus-jose-jwt - dependency-name: com.nimbusds:nimbus-jose-jwt
- dependency-name: io.spring.nullability:*
- dependency-name: org.python:jython - dependency-name: org.python:jython
- dependency-name: org.apache.directory.server:* - dependency-name: org.apache.directory.server:*
- dependency-name: org.apache.directory.shared:* - dependency-name: org.apache.directory.shared:*
@@ -74,34 +57,11 @@ updates:
- dependency-name: org.mockito:mockito-bom - dependency-name: org.mockito:mockito-bom
update-types: update-types:
- version-update:semver-major - version-update:semver-major
- dependency-name: com.gradle.enterprise
update-types:
- version-update:semver-major
- version-update:semver-minor
- dependency-name: '*' - dependency-name: '*'
update-types: update-types:
- version-update:semver-major - version-update:semver-major
- version-update:semver-minor - version-update:semver-minor
- package-ecosystem: npm
target-branch: 7.0.x
directory: /docs
schedule:
interval: weekly
labels:
- 'type: task'
- 'type: dependency-upgrade'
- 'in: build'
- package-ecosystem: github-actions
target-branch: 7.0.x
directory: /
schedule:
interval: weekly
labels:
- 'type: task'
- 'type: dependency-upgrade'
- 'in: build'
# main
- package-ecosystem: gradle - package-ecosystem: gradle
target-branch: main target-branch: main
directory: / directory: /
@@ -112,6 +72,7 @@ updates:
labels: labels:
- 'type: dependency-upgrade' - 'type: dependency-upgrade'
registries: registries:
- spring-milestones
- shibboleth - shibboleth
ignore: ignore:
- dependency-name: com.nimbusds:nimbus-jose-jwt - dependency-name: com.nimbusds:nimbus-jose-jwt
@@ -131,6 +92,17 @@ updates:
- dependency-name: '*' - dependency-name: '*'
update-types: update-types:
- version-update:semver-major - version-update:semver-major
- version-update:semver-minor
- package-ecosystem: npm
target-branch: docs-build
directory: /
schedule:
interval: weekly
labels:
- 'type: task'
- 'in: build'
- package-ecosystem: npm - package-ecosystem: npm
target-branch: main target-branch: main
directory: /docs directory: /docs
@@ -138,63 +110,12 @@ updates:
interval: weekly interval: weekly
labels: labels:
- 'type: task' - 'type: task'
- 'type: dependency-upgrade'
- 'in: build' - 'in: build'
- package-ecosystem: github-actions
target-branch: main
directory: /
schedule:
interval: weekly
labels:
- 'type: task'
- 'type: dependency-upgrade'
- 'in: build'
# docs-build
- package-ecosystem: gradle
target-branch: docs-build
directory: /
schedule:
interval: daily
time: '03:00'
timezone: Etc/UTC
labels:
- 'type: dependency-upgrade'
registries:
- shibboleth
ignore:
- dependency-name: com.nimbusds:nimbus-jose-jwt
- dependency-name: org.python:jython
- dependency-name: org.apache.directory.server:*
- dependency-name: org.apache.directory.shared:*
- dependency-name: org.junit:junit-bom
update-types:
- version-update:semver-major
- dependency-name: org.mockito:mockito-bom
update-types:
- version-update:semver-major
- dependency-name: com.gradle.enterprise
update-types:
- version-update:semver-major
- version-update:semver-minor
- dependency-name: '*'
update-types:
- version-update:semver-major
- package-ecosystem: npm - package-ecosystem: npm
target-branch: docs-build target-branch: 6.3.x
directory: / directory: /docs
schedule: schedule:
interval: weekly interval: weekly
labels: labels:
- 'type: task' - 'type: task'
- 'type: dependency-upgrade'
- 'in: build'
- package-ecosystem: github-actions
target-branch: docs-build
directory: /
schedule:
interval: weekly
labels:
- 'type: task'
- 'type: dependency-upgrade'
- 'in: build' - 'in: build'
@@ -1,16 +0,0 @@
name: Merge Dependabot PR
on:
pull_request:
branches:
- main
- '*.x'
run-name: Merge Dependabot PR ${{ github.ref_name }}
jobs:
merge-dependabot-pr:
permissions: write-all
uses: spring-io/spring-github-workflows/.github/workflows/spring-merge-dependabot-pr.yml@v7
with:
mergeArguments: --auto --rebase
+6 -4
View File
@@ -14,12 +14,14 @@ permissions:
jobs: jobs:
snapshot-test: snapshot-test:
name: Test Against Snapshots name: Test Against Snapshots
uses: spring-io/spring-security-release-tools/.github/workflows/test.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14 uses: spring-io/spring-security-release-tools/.github/workflows/test.yml@v1
strategy: strategy:
matrix: matrix:
include: include:
- java-version: 25 - java-version: 21-ea
toolchain: 25 toolchain: 21
- java-version: 17
toolchain: 17
with: with:
java-version: ${{ matrix.java-version }} java-version: ${{ matrix.java-version }}
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot,https://oss.sonatype.org/content/repositories/snapshots -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=7.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ --stacktrace test-args: --refresh-dependencies -PforceMavenRepositories=snapshot,https://oss.sonatype.org/content/repositories/snapshots -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=7.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ --stacktrace
@@ -31,6 +33,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Send Notification - name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14 uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with: with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }} webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
@@ -17,11 +17,11 @@ permissions:
jobs: jobs:
build: build:
name: Build name: Build
uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@v1
strategy: strategy:
matrix: matrix:
os: [ ubuntu-latest, windows-latest ] os: [ ubuntu-latest, windows-latest ]
jdk: [ 25 ] jdk: [ 17 ]
with: with:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
java-version: ${{ matrix.jdk }} java-version: ${{ matrix.jdk }}
@@ -30,24 +30,29 @@ jobs:
deploy-artifacts: deploy-artifacts:
name: Deploy Artifacts name: Deploy Artifacts
needs: [ build] needs: [ build]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@v1
with: with:
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }} should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
default-publish-milestones-central: true default-publish-milestones-central: true
java-version: 25 secrets: inherit
deploy-docs:
name: Deploy Docs
needs: [ build ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-docs.yml@v1
with:
should-deploy-docs: ${{ needs.build.outputs.should-deploy-artifacts }}
secrets: inherit secrets: inherit
deploy-schema: deploy-schema:
name: Deploy Schema name: Deploy Schema
needs: [ build ] needs: [ build ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@v1
with: with:
should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }} should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }}
java-version: 25
secrets: inherit secrets: inherit
perform-release: perform-release:
name: Perform Release name: Perform Release
needs: [ deploy-artifacts, deploy-schema ] needs: [ deploy-artifacts, deploy-docs, deploy-schema ]
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1
with: with:
should-perform-release: ${{ needs.deploy-artifacts.outputs.artifacts-deployed }} should-perform-release: ${{ needs.deploy-artifacts.outputs.artifacts-deployed }}
project-version: ${{ needs.deploy-artifacts.outputs.project-version }} project-version: ${{ needs.deploy-artifacts.outputs.project-version }}
@@ -55,7 +60,6 @@ jobs:
release-repo-url: https://repo1.maven.org/maven2 release-repo-url: https://repo1.maven.org/maven2
artifact-path: org/springframework/security/spring-security-core artifact-path: org/springframework/security/spring-security-core
slack-announcing-id: spring-security-announcing slack-announcing-id: spring-security-announcing
java-version: 25
secrets: inherit secrets: inherit
send-notification: send-notification:
name: Send Notification name: Send Notification
@@ -64,6 +68,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Send Notification - name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15 uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with: with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }} webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
-76
View File
@@ -1,76 +0,0 @@
name: Defer Issues
on:
workflow_dispatch:
permissions:
contents: read
jobs:
defer-issues:
name: Defer Issues
runs-on: ubuntu-latest
if: github.repository_owner == 'spring-projects'
permissions:
issues: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Compute Version
id: compute-version
uses: spring-io/spring-release-actions/compute-version@0.0.3
- name: Get Today's Release Version
id: todays-release
uses: spring-io/spring-release-actions/get-todays-release-version@0.0.3
with:
snapshot-version: ${{ steps.compute-version.outputs.version }}
milestone-repository: ${{ github.repository }}
milestone-token: ${{ secrets.GITHUB_TOKEN }}
- name: Compute Next Version
id: next-version
uses: spring-io/spring-release-actions/compute-next-version@0.0.3
with:
version: ${{ steps.todays-release.outputs.release-version }}
- name: Schedule Next Milestone
uses: spring-io/spring-release-actions/schedule-milestone@0.0.3
with:
version: ${{ steps.next-version.outputs.version }}
version-date: ${{ steps.next-version.outputs.version-date }}
repository: ${{ github.repository }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Move Open Issues to Next Milestone
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CURRENT_MILESTONE: ${{ steps.todays-release.outputs.release-version }}
NEXT_MILESTONE: ${{ steps.next-version.outputs.version }}
run: |
current_milestone_number=$(gh api repos/${{ github.repository }}/milestones \
--jq ".[] | select(.title == \"$CURRENT_MILESTONE\") | .number")
if [ -z "$current_milestone_number" ]; then
echo "No milestone found for $CURRENT_MILESTONE"
exit 0
fi
next_milestone_number=$(gh api repos/${{ github.repository }}/milestones \
--jq ".[] | select(.title == \"$NEXT_MILESTONE\") | .number")
if [ -z "$next_milestone_number" ]; then
echo "No milestone found for $NEXT_MILESTONE"
exit 1
fi
echo "Moving open issues from milestone '$CURRENT_MILESTONE' (#$current_milestone_number) to '$NEXT_MILESTONE' (#$next_milestone_number)"
page=1
while true; do
issues=$(gh api "repos/${{ github.repository }}/issues?milestone=$current_milestone_number&state=open&per_page=100&page=$page" \
--jq '.[].number')
if [ -z "$issues" ]; then
break
fi
for issue in $issues; do
echo "Moving issue/PR #$issue to milestone $NEXT_MILESTONE"
gh api repos/${{ github.repository }}/issues/$issue \
--method PATCH \
--field milestone=$next_milestone_number \
--silent
done
page=$((page + 1))
done
echo "Done."
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
if: github.repository_owner == 'spring-projects' if: github.repository_owner == 'spring-projects'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v4
with: with:
ref: docs-build ref: docs-build
fetch-depth: 1 fetch-depth: 1
+20 -6
View File
@@ -2,10 +2,6 @@ name: Finalize Release
on: on:
workflow_dispatch: # Manual trigger workflow_dispatch: # Manual trigger
inputs:
version:
description: The Spring Security release to finalize (e.g. 7.0.0-RC2)
required: true
env: env:
DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
@@ -14,14 +10,32 @@ permissions:
contents: read contents: read
jobs: jobs:
project-version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.project-version.outputs.version }}
steps:
- id: project-version
run: echo "version=$(grep '^version=' gradle.properties | cut -d'=' -f2)" >> $GITHUB_OUTPUT
perform-release: perform-release:
name: Perform Release name: Perform Release
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14 needs: [ project-version ]
uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1
with: with:
should-perform-release: true should-perform-release: true
project-version: ${{ inputs.version }} project-version: ${{ needs.project-version.outputs.version }}
milestone-repo-url: https://repo1.maven.org/maven2 milestone-repo-url: https://repo1.maven.org/maven2
release-repo-url: https://repo1.maven.org/maven2 release-repo-url: https://repo1.maven.org/maven2
artifact-path: org/springframework/security/spring-security-core artifact-path: org/springframework/security/spring-security-core
slack-announcing-id: spring-security-announcing slack-announcing-id: spring-security-announcing
secrets: inherit secrets: inherit
send-notification:
name: Send Notification
needs: [ perform-release ]
if: ${{ !success() }}
runs-on: ubuntu-latest
steps:
- name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
@@ -9,7 +9,6 @@ permissions:
jobs: jobs:
upgrade_wrapper: upgrade_wrapper:
name: Execution name: Execution
if: ${{ github.repository == 'spring-projects/spring-security' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Set up Git configuration - name: Set up Git configuration
@@ -20,14 +19,14 @@ jobs:
git config --global user.name 'github-actions[bot]' git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com' git config --global user.email 'github-actions[bot]@users.noreply.github.com'
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v4
- name: Set up JDK 25 - name: Set up JDK 17
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 uses: actions/setup-java@v4
with: with:
java-version: '25' java-version: '17'
distribution: 'temurin' distribution: 'temurin'
- name: Set up Gradle - name: Set up Gradle
uses: gradle/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 uses: gradle/gradle-build-action@v2
- name: Upgrade Wrappers - name: Upgrade Wrappers
run: ./gradlew clean upgradeGradleWrapperAll --continue -Porg.gradle.java.installations.auto-download=false run: ./gradlew clean upgradeGradleWrapperAll --continue -Porg.gradle.java.installations.auto-download=false
env: env:
@@ -30,6 +30,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Send Notification - name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14 uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with: with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }} webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
+8 -8
View File
@@ -11,11 +11,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }} if: ${{ github.repository == 'spring-projects/spring-security' }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@v4
- name: Set up gradle - name: Set up gradle
uses: spring-io/spring-gradle-build-action@efc55f07f4dfa22f2afd97f9ea1be4212eeed737 # v2.0.5 uses: spring-io/spring-gradle-build-action@v2
with: with:
java-version: '25' java-version: '17'
distribution: 'temurin' distribution: 'temurin'
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew clean build -PskipCheckExpectedBranchVersion --continue --scan run: ./gradlew clean build -PskipCheckExpectedBranchVersion --continue --scan
@@ -24,17 +24,17 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }} if: ${{ github.repository == 'spring-projects/spring-security' }}
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@v4
- name: Set up gradle - name: Set up gradle
uses: spring-io/spring-gradle-build-action@efc55f07f4dfa22f2afd97f9ea1be4212eeed737 # v2.0.5 uses: spring-io/spring-gradle-build-action@v2
with: with:
java-version: '25' java-version: '17'
distribution: 'temurin' distribution: 'temurin'
- name: Run Antora - name: Run Antora
run: ./gradlew -PbuildSrc.skipTests=true :spring-security-docs:antora run: ./gradlew -PbuildSrc.skipTests=true :spring-security-docs:antora
- name: Upload Docs - name: Upload Docs
id: upload id: upload
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 uses: actions/upload-artifact@v4
with: with:
name: docs name: docs
path: docs/build/site path: docs/build/site
@@ -46,6 +46,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Send Notification - name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14 uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with: with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }} webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
+2 -2
View File
@@ -11,11 +11,11 @@ jobs:
strategy: strategy:
matrix: matrix:
# List of active maintenance branches. # List of active maintenance branches.
branch: [ main, 7.0.x, 6.5.x, 6.4.x, 6.3.x ] branch: [ main, 6.5.x, 6.4.x, 6.3.x ]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@v4
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Dispatch - name: Dispatch
@@ -12,25 +12,23 @@ permissions:
jobs: jobs:
update-antora-ui-spring: update-antora-ui-spring:
name: Update on Supported Branches
if: ${{ github.repository == 'spring-projects/spring-security' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Update on Supported Branches
strategy: strategy:
matrix: matrix:
branch: [ '6.5.x', '7.0.x', 'main' ] branch: [ '5.8.x', '6.2.x', '6.3.x', 'main' ]
steps: steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@415e2b11a766ba64799fffb5c97a4f7e17f677cf - uses: spring-io/spring-doc-actions/update-antora-spring-ui@e28269199d1d27975cf7f65e16d6095c555b3cd0
name: Update name: Update
with: with:
docs-branch: ${{ matrix.branch }} docs-branch: ${{ matrix.branch }}
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
antora-file-path: 'docs/antora-playbook.yml' antora-file-path: 'docs/antora-playbook.yml'
update-antora-ui-spring-docs-build: update-antora-ui-spring-docs-build:
name: Update on docs-build
if: ${{ github.repository == 'spring-projects/spring-security' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: Update on docs-build
steps: steps:
- uses: spring-io/spring-doc-actions/update-antora-spring-ui@415e2b11a766ba64799fffb5c97a4f7e17f677cf - uses: spring-io/spring-doc-actions/update-antora-spring-ui@e28269199d1d27975cf7f65e16d6095c555b3cd0
name: Update name: Update
with: with:
docs-branch: 'docs-build' docs-branch: 'docs-build'
@@ -9,7 +9,7 @@ permissions:
jobs: jobs:
update-scheduled-release-version: update-scheduled-release-version:
name: Update Scheduled Release Version name: Update Scheduled Release Version
uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14 uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@v1
secrets: inherit secrets: inherit
send-notification: send-notification:
name: Send Notification name: Send Notification
@@ -18,6 +18,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Send Notification - name: Send Notification
uses: spring-io/spring-security-release-tools/.github/actions/send-notification@729fed56d42122f88583aff1be35c0800b7d77e9 # v1.0.14 uses: spring-io/spring-security-release-tools/.github/actions/send-notification@v1
with: with:
webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }} webhook-url: ${{ secrets.SPRING_SECURITY_CI_GCHAT_WEBHOOK_URL }}
+1 -1
View File
@@ -3,4 +3,4 @@
# See https://sdkman.io/usage#config # See https://sdkman.io/usage#config
# A summary is to add the following to ~/.sdkman/etc/config # A summary is to add the following to ~/.sdkman/etc/config
# sdkman_auto_env=true # sdkman_auto_env=true
java=25-librca java=17.0.3-tem
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"java.gradle.buildServer.enabled": "off" "java.import.gradle.enabled": false
} }
+2 -2
View File
@@ -31,7 +31,7 @@ If you have a question, check Stack Overflow using
https://stackoverflow.com/questions/tagged/spring-security+or+spring-ldap+or+spring-authorization-server+or+spring-session?tab=Newest[this list of tags]. https://stackoverflow.com/questions/tagged/spring-security+or+spring-ldap+or+spring-authorization-server+or+spring-session?tab=Newest[this list of tags].
Find an existing discussion, or start a new one if necessary. Find an existing discussion, or start a new one if necessary.
If you believe there is an issue, search through https://github.com/spring-projects/spring-security/issues[existing issues] trying a few different ways to find discussions, past or current, that are related to the issue. If you believe there is an issue, search through https://github.com/spring-projects/spring-security/issues[existing issues] trying a few different ways to find discussions, past or current, that are related to the issue.
Reading those discussions helps you to learn about the issue, and helps us to make a decision. Reading those discussions helps you to learn about the issue, and helps us to make a decision.
[[find-an-issue]] [[find-an-issue]]
@@ -94,7 +94,7 @@ Don't worry if you don't get them all correct the first time, we will help you.
1. [[sign-cla]] All commits must include a __Signed-off-by__ trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin. 1. [[sign-cla]] All commits must include a __Signed-off-by__ trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin.
For additional details, please refer to the blog post https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring]. For additional details, please refer to the blog post https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring].
2. [[create-an-issue-list]] Must you https://github.com/spring-projects/spring-security/issues/new/choose[create an issue] first? No, but it is recommended for features and larger bug fixes. It's easier to discuss with the team first to determine the right fix or enhancement. 2. [[create-an-issue-list]] Must you https://github.com/spring-projects/spring-security/issues/new/choose[create an issue] first? No, but it is recommended for features and larger bug fixes. It's easier discuss with the team first to determine the right fix or enhancement.
For typos and straightforward bug fixes, starting with a pull request is encouraged. For typos and straightforward bug fixes, starting with a pull request is encouraged.
Please include a description for context and motivation. Please include a description for context and motivation.
Note that the team may close your pull request if it's not a fit for the project. Note that the team may close your pull request if it's not a fit for the project.
-21
View File
@@ -68,27 +68,6 @@ The https://github.com/spring-projects/spring-security/tree/docs-build[playbook
Discover more commands with `./gradlew tasks`. Discover more commands with `./gradlew tasks`.
=== IDE setup (IntelliJ)
No special steps are needed to open Spring Security in IntelliJ.
=== IDE setup (Eclipse and VS Code)
To work in Eclipse or VS Code, first generate Eclipse metadata so you can import the project into Eclipse or VS Code:
[indent=0]
----
./gradlew cleanEclipse eclipse
----
If you have not built the project yet, run `./gradlew publishToMavenLocal` first so dependencies are resolved.
*VS Code:* Open the repository root as a folder. The repository includes `.vscode/settings.json` which disables automatic Gradle import so that the generated Eclipse metadata (`.classpath`, `.project`) is used. Do not use the Gradle for Java extension to import the project.
*Eclipse:* File → Import → General → Existing Projects into Workspace, then select the repository root.
The build uses a custom Eclipse plugin to work around Gradle dependency cycles that confuse IDE metadata generation. You may see Eclipse warnings about `xml-apis` from some test dependencies; those are excluded in the build and can be ignored.
== Getting Support == Getting Support
Check out the https://stackoverflow.com/questions/tagged/spring-security[Spring Security tags on Stack Overflow]. Check out the https://stackoverflow.com/questions/tagged/spring-security[Spring Security tags on Stack Overflow].
https://spring.io/support[Commercial support] is available too. https://spring.io/support[Commercial support] is available too.
-5
View File
@@ -1,8 +1,3 @@
plugins {
id 'compile-warnings-error'
id 'javadoc-warnings-error'
}
apply plugin: 'io.spring.convention.spring-module' apply plugin: 'io.spring.convention.spring-module'
dependencies { dependencies {
@@ -31,7 +31,6 @@ import org.jspecify.annotations.Nullable;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.method.AbstractFallbackMethodSecurityMetadataSource; import org.springframework.security.access.method.AbstractFallbackMethodSecurityMetadataSource;
import org.springframework.util.StringUtils;
/** /**
* Sources method security metadata from major JSR 250 security annotations. * Sources method security metadata from major JSR 250 security annotations.
@@ -109,7 +108,7 @@ public class Jsr250MethodSecurityMetadataSource extends AbstractFallbackMethodSe
if (role == null) { if (role == null) {
return role; return role;
} }
if (!StringUtils.hasLength(this.defaultRolePrefix)) { if (this.defaultRolePrefix == null || this.defaultRolePrefix.length() == 0) {
return role; return role;
} }
if (role.startsWith(this.defaultRolePrefix)) { if (role.startsWith(this.defaultRolePrefix)) {
@@ -53,9 +53,7 @@ import org.springframework.util.CollectionUtils;
* *
* @author Ben Alex * @author Ben Alex
* @author Luke Taylor * @author Luke Taylor
* @deprecated Use * @deprecated Use {@link EnableMethodSecurity} or publish interceptors directly
* <code>org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity</code>
* or publish interceptors directly
*/ */
@NullUnmarked @NullUnmarked
@Deprecated @Deprecated
@@ -39,7 +39,7 @@ interface EvaluationContextPostProcessor<I> {
* that was passed in. * that was passed in.
* @param context the original {@link EvaluationContext} * @param context the original {@link EvaluationContext}
* @param invocation the security invocation object (i.e. Message) * @param invocation the security invocation object (i.e. Message)
* @return the updated context. * @return the upated context.
*/ */
EvaluationContext postProcess(EvaluationContext context, I invocation); EvaluationContext postProcess(EvaluationContext context, I invocation);
-6
View File
@@ -1,9 +1,3 @@
plugins {
id 'compile-warnings-error'
id 'javadoc-warnings-error'
id 'security-nullability'
}
apply plugin: 'io.spring.convention.spring-module' apply plugin: 'io.spring.convention.spring-module'
dependencies { dependencies {
@@ -23,7 +23,6 @@ import java.util.Locale;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.core.log.LogMessage; import org.springframework.core.log.LogMessage;
import org.springframework.security.access.PermissionEvaluator; import org.springframework.security.access.PermissionEvaluator;
@@ -45,7 +44,7 @@ import org.springframework.security.core.Authentication;
/** /**
* Used by Spring Security's expression-based access control implementation to evaluate * Used by Spring Security's expression-based access control implementation to evaluate
* permissions for a particular object using the ACL module. Similar in behaviour to * permissions for a particular object using the ACL module. Similar in behaviour to
* <code> org.springframework.security.acls.AclEntryVoter AclEntryVoter </code> * {@link org.springframework.security.acls.AclEntryVoter AclEntryVoter}.
* *
* @author Luke Taylor * @author Luke Taylor
* @since 3.0 * @since 3.0
@@ -74,7 +73,7 @@ public class AclPermissionEvaluator implements PermissionEvaluator {
* be overridden using a null check in the expression itself). * be overridden using a null check in the expression itself).
*/ */
@Override @Override
public boolean hasPermission(Authentication authentication, @Nullable Object domainObject, Object permission) { public boolean hasPermission(Authentication authentication, Object domainObject, Object permission) {
if (domainObject == null) { if (domainObject == null) {
return false; return false;
} }
@@ -1,79 +0,0 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.acls.aot.hint;
import java.util.stream.Stream;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.security.acls.domain.AclImpl;
import org.springframework.security.acls.domain.AuditLogger;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.AccessControlEntry;
import org.springframework.security.acls.model.Acl;
import org.springframework.security.acls.model.AuditableAccessControlEntry;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.Sid;
/**
* {@link RuntimeHintsRegistrar} for ACL (Access Control List) classes.
*
* @author Josh Long
*/
class AclRuntimeHints implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
registerAclDomainHints(hints);
registerJdbcSchemaHints(hints);
}
private void registerAclDomainHints(RuntimeHints hints) {
// Register core ACL domain types
Stream
.of(Acl.class, AccessControlEntry.class, AuditableAccessControlEntry.class, ObjectIdentity.class, Sid.class,
AclImpl.class, AccessControlEntry.class, AuditLogger.class, ObjectIdentityImpl.class,
PrincipalSid.class, GrantedAuthoritySid.class, BasePermission.class)
.forEach((c) -> hints.reflection()
.registerType(TypeReference.of(c),
(builder) -> builder.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.ACCESS_DECLARED_FIELDS)));
}
private void registerJdbcSchemaHints(RuntimeHints hints) {
String[] sqlFiles = new String[] { "createAclSchema.sql", "createAclSchemaMySQL.sql",
"createAclSchemaOracle.sql", "createAclSchemaPostgres.sql", "createAclSchemaSqlServer.sql",
"createAclSchemaWithAclClassIdType.sql", "select.sql" };
for (String sqlFile : sqlFiles) {
Resource sqlResource = new ClassPathResource(sqlFile);
if (sqlResource.exists()) {
hints.resources().registerResource(sqlResource);
}
}
}
}
@@ -1,23 +0,0 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* AOT and native image hint support for ACLs.
*/
@NullMarked
package org.springframework.security.acls.aot.hint;
import org.jspecify.annotations.NullMarked;
@@ -18,8 +18,6 @@ package org.springframework.security.acls.domain;
import java.io.Serializable; import java.io.Serializable;
import org.jspecify.annotations.Nullable;
import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.AccessControlEntry;
import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.Acl;
import org.springframework.security.acls.model.AuditableAccessControlEntry; import org.springframework.security.acls.model.AuditableAccessControlEntry;
@@ -38,7 +36,7 @@ public class AccessControlEntryImpl implements AccessControlEntry, AuditableAcce
private Permission permission; private Permission permission;
private final @Nullable Serializable id; private final Serializable id;
private final Sid sid; private final Sid sid;
@@ -48,7 +46,7 @@ public class AccessControlEntryImpl implements AccessControlEntry, AuditableAcce
private final boolean granting; private final boolean granting;
public AccessControlEntryImpl(@Nullable Serializable id, Acl acl, Sid sid, Permission permission, boolean granting, public AccessControlEntryImpl(Serializable id, Acl acl, Sid sid, Permission permission, boolean granting,
boolean auditSuccess, boolean auditFailure) { boolean auditSuccess, boolean auditFailure) {
Assert.notNull(acl, "Acl required"); Assert.notNull(acl, "Acl required");
Assert.notNull(sid, "Sid required"); Assert.notNull(sid, "Sid required");
@@ -135,7 +133,7 @@ public class AccessControlEntryImpl implements AccessControlEntry, AuditableAcce
} }
@Override @Override
public @Nullable Serializable getId() { public Serializable getId() {
return this.id; return this.id;
} }
@@ -20,7 +20,7 @@ import org.springframework.security.acls.model.Acl;
/** /**
* Strategy used by {@link AclImpl} to determine whether a principal is permitted to call * Strategy used by {@link AclImpl} to determine whether a principal is permitted to call
* administrative methods on the <code>AclImpl</code>. * adminstrative methods on the <code>AclImpl</code>.
* *
* @author Ben Alex * @author Ben Alex
*/ */
@@ -99,8 +99,7 @@ public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy {
Authentication authentication = context.getAuthentication(); Authentication authentication = context.getAuthentication();
// Check if authorized by virtue of ACL ownership // Check if authorized by virtue of ACL ownership
Sid currentUser = createCurrentUser(authentication); Sid currentUser = createCurrentUser(authentication);
Sid owner = acl.getOwner(); if (currentUser.equals(acl.getOwner())
if (owner != null && currentUser.equals(owner)
&& ((changeType == CHANGE_GENERAL) || (changeType == CHANGE_OWNERSHIP))) { && ((changeType == CHANGE_GENERAL) || (changeType == CHANGE_OWNERSHIP))) {
return; return;
} }
@@ -109,8 +108,8 @@ public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy {
Collection<? extends GrantedAuthority> reachableGrantedAuthorities = this.roleHierarchy Collection<? extends GrantedAuthority> reachableGrantedAuthorities = this.roleHierarchy
.getReachableGrantedAuthorities(authentication.getAuthorities()); .getReachableGrantedAuthorities(authentication.getAuthorities());
Set<String> authorities = AuthorityUtils.authorityListToSet(reachableGrantedAuthorities); Set<String> authorities = AuthorityUtils.authorityListToSet(reachableGrantedAuthorities);
if (owner instanceof GrantedAuthoritySid if (acl.getOwner() instanceof GrantedAuthoritySid
&& authorities.contains(((GrantedAuthoritySid) owner).getGrantedAuthority())) { && authorities.contains(((GrantedAuthoritySid) acl.getOwner()).getGrantedAuthority())) {
return; return;
} }
@@ -20,8 +20,6 @@ import java.io.Serializable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.jspecify.annotations.Nullable;
import org.springframework.security.acls.model.AccessControlEntry; import org.springframework.security.acls.model.AccessControlEntry;
import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.Acl;
import org.springframework.security.acls.model.AuditableAcl; import org.springframework.security.acls.model.AuditableAcl;
@@ -43,7 +41,7 @@ import org.springframework.util.ObjectUtils;
*/ */
public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl { public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
private @Nullable Acl parentAcl; private Acl parentAcl;
private transient AclAuthorizationStrategy aclAuthorizationStrategy; private transient AclAuthorizationStrategy aclAuthorizationStrategy;
@@ -56,10 +54,10 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
private Serializable id; private Serializable id;
// OwnershipAcl // OwnershipAcl
private @Nullable Sid owner; private Sid owner;
// includes all SIDs the WHERE clause covered, even if there was no ACE for a SID // includes all SIDs the WHERE clause covered, even if there was no ACE for a SID
private @Nullable List<Sid> loadedSids = null; private List<Sid> loadedSids = null;
private boolean entriesInheriting = true; private boolean entriesInheriting = true;
@@ -99,8 +97,8 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
* @param owner the owner (required) * @param owner the owner (required)
*/ */
public AclImpl(ObjectIdentity objectIdentity, Serializable id, AclAuthorizationStrategy aclAuthorizationStrategy, public AclImpl(ObjectIdentity objectIdentity, Serializable id, AclAuthorizationStrategy aclAuthorizationStrategy,
PermissionGrantingStrategy grantingStrategy, @Nullable Acl parentAcl, @Nullable List<Sid> loadedSids, PermissionGrantingStrategy grantingStrategy, Acl parentAcl, List<Sid> loadedSids, boolean entriesInheriting,
boolean entriesInheriting, Sid owner) { Sid owner) {
Assert.notNull(objectIdentity, "Object Identity required"); Assert.notNull(objectIdentity, "Object Identity required");
Assert.notNull(id, "Id required"); Assert.notNull(id, "Id required");
Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required"); Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
@@ -119,7 +117,7 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
* Private no-argument constructor for use by reflection-based persistence tools along * Private no-argument constructor for use by reflection-based persistence tools along
* with field-level access. * with field-level access.
*/ */
@SuppressWarnings({ "unused", "NullAway.Init" }) @SuppressWarnings("unused")
private AclImpl() { private AclImpl() {
} }
@@ -201,7 +199,7 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
} }
@Override @Override
public boolean isSidLoaded(@Nullable List<Sid> sids) { public boolean isSidLoaded(List<Sid> sids) {
// If loadedSides is null, this indicates all SIDs were loaded // If loadedSides is null, this indicates all SIDs were loaded
// Also return true if the caller didn't specify a SID to find // Also return true if the caller didn't specify a SID to find
if ((this.loadedSids == null) || (sids == null) || sids.isEmpty()) { if ((this.loadedSids == null) || (sids == null) || sids.isEmpty()) {
@@ -240,19 +238,19 @@ public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl {
} }
@Override @Override
public @Nullable Sid getOwner() { public Sid getOwner() {
return this.owner; return this.owner;
} }
@Override @Override
public void setParent(@Nullable Acl newParent) { public void setParent(Acl newParent) {
this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL); this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL);
Assert.isTrue(newParent == null || !newParent.equals(this), "Cannot be the parent of yourself"); Assert.isTrue(newParent == null || !newParent.equals(this), "Cannot be the parent of yourself");
this.parentAcl = newParent; this.parentAcl = newParent;
} }
@Override @Override
public @Nullable Acl getParentAcl() { public Acl getParentAcl() {
return this.parentAcl; return this.parentAcl;
} }
@@ -42,7 +42,7 @@ public class GrantedAuthoritySid implements Sid {
public GrantedAuthoritySid(GrantedAuthority grantedAuthority) { public GrantedAuthoritySid(GrantedAuthority grantedAuthority) {
Assert.notNull(grantedAuthority, "GrantedAuthority required"); Assert.notNull(grantedAuthority, "GrantedAuthority required");
Assert.notNull(grantedAuthority.getAuthority(), Assert.notNull(grantedAuthority.getAuthority(),
"This Sid is only compatible with GrantedAuthority that provide a non-null getAuthority()"); "This Sid is only compatible with GrantedAuthoritys that provide a non-null getAuthority()");
this.grantedAuthority = grantedAuthority.getAuthority(); this.grantedAuthority = grantedAuthority.getAuthority();
} }
@@ -18,8 +18,6 @@ package org.springframework.security.acls.domain;
import java.io.Serializable; import java.io.Serializable;
import org.jspecify.annotations.Nullable;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
import org.springframework.security.acls.model.AclCache; import org.springframework.security.acls.model.AclCache;
import org.springframework.security.acls.model.MutableAcl; import org.springframework.security.acls.model.MutableAcl;
@@ -80,13 +78,13 @@ public class SpringCacheBasedAclCache implements AclCache {
} }
@Override @Override
public @Nullable MutableAcl getFromCache(ObjectIdentity objectIdentity) { public MutableAcl getFromCache(ObjectIdentity objectIdentity) {
Assert.notNull(objectIdentity, "ObjectIdentity required"); Assert.notNull(objectIdentity, "ObjectIdentity required");
return getFromCache((Object) objectIdentity); return getFromCache((Object) objectIdentity);
} }
@Override @Override
public @Nullable MutableAcl getFromCache(Serializable pk) { public MutableAcl getFromCache(Serializable pk) {
Assert.notNull(pk, "Primary key (identifier) required"); Assert.notNull(pk, "Primary key (identifier) required");
return getFromCache((Object) pk); return getFromCache((Object) pk);
} }
@@ -103,16 +101,12 @@ public class SpringCacheBasedAclCache implements AclCache {
this.cache.put(acl.getId(), acl); this.cache.put(acl.getId(), acl);
} }
private @Nullable MutableAcl getFromCache(Object key) { private MutableAcl getFromCache(Object key) {
Cache.ValueWrapper element = this.cache.get(key); Cache.ValueWrapper element = this.cache.get(key);
if (element == null) { if (element == null) {
return null; return null;
} }
Object value = element.get(); return initializeTransientFields((MutableAcl) element.get());
if (value == null) {
return null;
}
return initializeTransientFields((MutableAcl) value);
} }
private MutableAcl initializeTransientFields(MutableAcl value) { private MutableAcl initializeTransientFields(MutableAcl value) {
@@ -17,7 +17,4 @@
/** /**
* Basic implementation of access control lists (ACLs) interfaces. * Basic implementation of access control lists (ACLs) interfaces.
*/ */
@NullMarked
package org.springframework.security.acls.domain; package org.springframework.security.acls.domain;
import org.jspecify.annotations.NullMarked;
@@ -23,7 +23,6 @@ import java.util.UUID;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
@@ -68,10 +67,10 @@ class AclClassIdUtils {
* @return The identifier in the appropriate target Java type. Typically Long or UUID. * @return The identifier in the appropriate target Java type. Typically Long or UUID.
* @throws SQLException * @throws SQLException
*/ */
@Nullable Serializable identifierFrom(Serializable identifier, ResultSet resultSet) throws SQLException { Serializable identifierFrom(Serializable identifier, ResultSet resultSet) throws SQLException {
Class<? extends Serializable> classIdType = classIdTypeFrom(resultSet); if (isString(identifier) && hasValidClassIdType(resultSet)
if (isString(identifier) && classIdType != null && canConvertFromStringTo(classIdType)) { && canConvertFromStringTo(classIdTypeFrom(resultSet))) {
return convertFromStringTo((String) identifier, classIdType); return convertFromStringTo((String) identifier, classIdTypeFrom(resultSet));
} }
// Assume it should be a Long type // Assume it should be a Long type
return convertToLong(identifier); return convertToLong(identifier);
@@ -87,38 +86,28 @@ class AclClassIdUtils {
} }
} }
private @Nullable Class<? extends Serializable> classIdTypeFrom(ResultSet resultSet) throws SQLException { private <T extends Serializable> Class<T> classIdTypeFrom(ResultSet resultSet) throws SQLException {
try { return classIdTypeFrom(resultSet.getString(DEFAULT_CLASS_ID_TYPE_COLUMN_NAME));
return classIdTypeFrom(resultSet.getString(DEFAULT_CLASS_ID_TYPE_COLUMN_NAME));
}
catch (SQLException ex) {
log.debug("Unable to obtain the class id type", ex);
return null;
}
} }
private @Nullable Class<? extends Serializable> classIdTypeFrom(String className) { private <T extends Serializable> Class<T> classIdTypeFrom(String className) {
if (className == null) { if (className == null) {
return null; return null;
} }
try { try {
return Class.forName(className).asSubclass(Serializable.class); return (Class) Class.forName(className);
} }
catch (ClassNotFoundException ex) { catch (ClassNotFoundException ex) {
log.debug("Unable to find class id type on classpath", ex); log.debug("Unable to find class id type on classpath", ex);
return null; return null;
} }
catch (ClassCastException ex) {
log.debug("Class id type is not a Serializable type", ex);
return null;
}
} }
private <T> boolean canConvertFromStringTo(Class<T> targetType) { private <T> boolean canConvertFromStringTo(Class<T> targetType) {
return this.conversionService.canConvert(String.class, targetType); return this.conversionService.canConvert(String.class, targetType);
} }
private <T extends Serializable> @Nullable T convertFromStringTo(String identifier, Class<T> targetType) { private <T extends Serializable> T convertFromStringTo(String identifier, Class<T> targetType) {
return this.conversionService.convert(identifier, targetType); return this.conversionService.convert(identifier, targetType);
} }
@@ -132,7 +121,7 @@ class AclClassIdUtils {
* exception occurred * exception occurred
* @throws IllegalArgumentException if targetType is null * @throws IllegalArgumentException if targetType is null
*/ */
private @Nullable Long convertToLong(Serializable identifier) { private Long convertToLong(Serializable identifier) {
if (this.conversionService.canConvert(identifier.getClass(), Long.class)) { if (this.conversionService.canConvert(identifier.getClass(), Long.class)) {
return this.conversionService.convert(identifier, Long.class); return this.conversionService.convert(identifier, Long.class);
} }
@@ -151,10 +140,10 @@ class AclClassIdUtils {
private static class StringToLongConverter implements Converter<String, Long> { private static class StringToLongConverter implements Converter<String, Long> {
@Override @Override
public Long convert(@Nullable String identifierAsString) { public Long convert(String identifierAsString) {
if (identifierAsString == null) { if (identifierAsString == null) {
throw new ConversionFailedException(TypeDescriptor.valueOf(String.class), throw new ConversionFailedException(TypeDescriptor.valueOf(String.class),
TypeDescriptor.valueOf(Long.class), identifierAsString, new NullPointerException()); TypeDescriptor.valueOf(Long.class), null, null);
} }
return Long.parseLong(identifierAsString); return Long.parseLong(identifierAsString);
@@ -165,10 +154,10 @@ class AclClassIdUtils {
private static class StringToUUIDConverter implements Converter<String, UUID> { private static class StringToUUIDConverter implements Converter<String, UUID> {
@Override @Override
public UUID convert(@Nullable String identifierAsString) { public UUID convert(String identifierAsString) {
if (identifierAsString == null) { if (identifierAsString == null) {
throw new ConversionFailedException(TypeDescriptor.valueOf(String.class), throw new ConversionFailedException(TypeDescriptor.valueOf(String.class),
TypeDescriptor.valueOf(UUID.class), identifierAsString, new NullPointerException()); TypeDescriptor.valueOf(UUID.class), null, null);
} }
return UUID.fromString(identifierAsString); return UUID.fromString(identifierAsString);
@@ -31,8 +31,6 @@ import java.util.Set;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.ConversionException; import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
@@ -226,8 +224,7 @@ public class BasicLookupStrategy implements LookupStrategy {
* @param findNow Long-based primary keys to retrieve * @param findNow Long-based primary keys to retrieve
* @param sids * @param sids
*/ */
private void lookupPrimaryKeys(final Map<Serializable, Acl> acls, final Set<Long> findNow, private void lookupPrimaryKeys(final Map<Serializable, Acl> acls, final Set<Long> findNow, final List<Sid> sids) {
final @Nullable List<Sid> sids) {
Assert.notNull(acls, "ACLs are required"); Assert.notNull(acls, "ACLs are required");
Assert.notEmpty(findNow, "Items to find now required"); Assert.notEmpty(findNow, "Items to find now required");
String sql = computeRepeatingSql(this.lookupPrimaryKeysWhereClause, findNow.size()); String sql = computeRepeatingSql(this.lookupPrimaryKeysWhereClause, findNow.size());
@@ -267,7 +264,7 @@ public class BasicLookupStrategy implements LookupStrategy {
* automatically create entries if required) * automatically create entries if required)
*/ */
@Override @Override
public final Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, @Nullable List<Sid> sids) { public final Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, List<Sid> sids) {
Assert.isTrue(this.batchSize >= 1, "BatchSize must be >= 1"); Assert.isTrue(this.batchSize >= 1, "BatchSize must be >= 1");
Assert.notEmpty(objects, "Objects to lookup required"); Assert.notEmpty(objects, "Objects to lookup required");
// Map<ObjectIdentity,Acl> // Map<ObjectIdentity,Acl>
@@ -326,7 +323,7 @@ public class BasicLookupStrategy implements LookupStrategy {
* properly-configured parent ACLs. * properly-configured parent ACLs.
*/ */
private Map<ObjectIdentity, Acl> lookupObjectIdentities(final Collection<ObjectIdentity> objectIdentities, private Map<ObjectIdentity, Acl> lookupObjectIdentities(final Collection<ObjectIdentity> objectIdentities,
@Nullable List<Sid> sids) { List<Sid> sids) {
Assert.notEmpty(objectIdentities, "Must provide identities to lookup"); Assert.notEmpty(objectIdentities, "Must provide identities to lookup");
// contains Acls with StubAclParents // contains Acls with StubAclParents
@@ -402,10 +399,8 @@ public class BasicLookupStrategy implements LookupStrategy {
} }
// Now we have the parent (if there is one), create the true AclImpl // Now we have the parent (if there is one), create the true AclImpl
Sid owner = inputAcl.getOwner();
Assert.isTrue(owner != null, "Owner is required");
AclImpl result = new AclImpl(inputAcl.getObjectIdentity(), inputAcl.getId(), this.aclAuthorizationStrategy, AclImpl result = new AclImpl(inputAcl.getObjectIdentity(), inputAcl.getId(), this.aclAuthorizationStrategy,
this.grantingStrategy, parent, null, inputAcl.isEntriesInheriting(), owner); this.grantingStrategy, parent, null, inputAcl.isEntriesInheriting(), inputAcl.getOwner());
// Copy the "aces" from the input to the destination // Copy the "aces" from the input to the destination
@@ -511,9 +506,9 @@ public class BasicLookupStrategy implements LookupStrategy {
private final Map<Serializable, Acl> acls; private final Map<Serializable, Acl> acls;
private final @Nullable List<Sid> sids; private final List<Sid> sids;
ProcessResultSet(Map<Serializable, Acl> acls, @Nullable List<Sid> sids) { ProcessResultSet(Map<Serializable, Acl> acls, List<Sid> sids) {
Assert.notNull(acls, "ACLs cannot be null"); Assert.notNull(acls, "ACLs cannot be null");
this.acls = acls; this.acls = acls;
this.sids = sids; // can be null this.sids = sids; // can be null
@@ -584,9 +579,6 @@ public class BasicLookupStrategy implements LookupStrategy {
// target id type, e.g. UUID. // target id type, e.g. UUID.
Serializable identifier = (Serializable) rs.getObject("object_id_identity"); Serializable identifier = (Serializable) rs.getObject("object_id_identity");
identifier = BasicLookupStrategy.this.aclClassIdUtils.identifierFrom(identifier, rs); identifier = BasicLookupStrategy.this.aclClassIdUtils.identifierFrom(identifier, rs);
if (identifier == null) {
throw new IllegalStateException("Identifier cannot be null");
}
ObjectIdentity objectIdentity = BasicLookupStrategy.this.objectIdentityGenerator ObjectIdentity objectIdentity = BasicLookupStrategy.this.objectIdentityGenerator
.createObjectIdentity(identifier, rs.getString("class")); .createObjectIdentity(identifier, rs.getString("class"));
@@ -678,7 +670,7 @@ public class BasicLookupStrategy implements LookupStrategy {
} }
@Override @Override
public boolean isSidLoaded(@Nullable List<Sid> sids) { public boolean isSidLoaded(List<Sid> sids) {
throw new UnsupportedOperationException("Stub only"); throw new UnsupportedOperationException("Stub only");
} }
@@ -27,7 +27,6 @@ import javax.sql.DataSource;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.ConversionService;
import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcOperations;
@@ -99,7 +98,7 @@ public class JdbcAclService implements AclService {
} }
@Override @Override
public @Nullable List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity) { public List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity) {
Object[] args = { parentIdentity.getIdentifier().toString(), parentIdentity.getType() }; Object[] args = { parentIdentity.getIdentifier().toString(), parentIdentity.getType() };
List<ObjectIdentity> objects = this.jdbcOperations.query(this.findChildrenSql, List<ObjectIdentity> objects = this.jdbcOperations.query(this.findChildrenSql,
(rs, rowNum) -> mapObjectIdentityRow(rs), args); (rs, rowNum) -> mapObjectIdentityRow(rs), args);
@@ -110,14 +109,11 @@ public class JdbcAclService implements AclService {
String javaType = rs.getString("class"); String javaType = rs.getString("class");
Serializable identifier = (Serializable) rs.getObject("obj_id"); Serializable identifier = (Serializable) rs.getObject("obj_id");
identifier = this.aclClassIdUtils.identifierFrom(identifier, rs); identifier = this.aclClassIdUtils.identifierFrom(identifier, rs);
if (identifier == null) {
throw new IllegalStateException("Identifier cannot be null");
}
return this.objectIdentityGenerator.createObjectIdentity(identifier, javaType); return this.objectIdentityGenerator.createObjectIdentity(identifier, javaType);
} }
@Override @Override
public Acl readAclById(ObjectIdentity object, @Nullable List<Sid> sids) throws NotFoundException { public Acl readAclById(ObjectIdentity object, List<Sid> sids) throws NotFoundException {
Map<ObjectIdentity, Acl> map = readAclsById(Collections.singletonList(object), sids); Map<ObjectIdentity, Acl> map = readAclsById(Collections.singletonList(object), sids);
Assert.isTrue(map.containsKey(object), Assert.isTrue(map.containsKey(object),
() -> "There should have been an Acl entry for ObjectIdentity " + object); () -> "There should have been an Acl entry for ObjectIdentity " + object);
@@ -135,7 +131,7 @@ public class JdbcAclService implements AclService {
} }
@Override @Override
public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, @Nullable List<Sid> sids) public Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, List<Sid> sids)
throws NotFoundException { throws NotFoundException {
Map<ObjectIdentity, Acl> result = this.lookupStrategy.readAclsById(objects, sids); Map<ObjectIdentity, Acl> result = this.lookupStrategy.readAclsById(objects, sids);
// Check every requested object identity was found (throw NotFoundException if // Check every requested object identity was found (throw NotFoundException if
@@ -164,7 +160,7 @@ public class JdbcAclService implements AclService {
this.findChildrenSql = DEFAULT_SELECT_ACL_WITH_PARENT_SQL_WITH_CLASS_ID_TYPE; this.findChildrenSql = DEFAULT_SELECT_ACL_WITH_PARENT_SQL_WITH_CLASS_ID_TYPE;
} }
else { else {
log.debug("Find children statement has already been overridden, so not overriding the default"); log.debug("Find children statement has already been overridden, so not overridding the default");
} }
} }
} }
@@ -22,8 +22,6 @@ import java.util.List;
import javax.sql.DataSource; import javax.sql.DataSource;
import org.jspecify.annotations.Nullable;
import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.security.acls.domain.AccessControlEntryImpl; import org.springframework.security.acls.domain.AccessControlEntryImpl;
@@ -52,7 +50,7 @@ import org.springframework.util.Assert;
* The default settings are for HSQLDB. If you are using a different database you will * The default settings are for HSQLDB. If you are using a different database you will
* probably need to set the {@link #setSidIdentityQuery(String) sidIdentityQuery} and * probably need to set the {@link #setSidIdentityQuery(String) sidIdentityQuery} and
* {@link #setClassIdentityQuery(String) classIdentityQuery} properties appropriately. The * {@link #setClassIdentityQuery(String) classIdentityQuery} properties appropriately. The
* other queries, SQL inserts and updates can also be customized to accommodate schema * other queries, SQL inserts and updates can also be customized to accomodate schema
* variations, but must produce results consistent with those expected by the defaults. * variations, but must produce results consistent with those expected by the defaults.
* <p> * <p>
* See the appendix of the Spring Security reference manual for more information on the * See the appendix of the Spring Security reference manual for more information on the
@@ -122,7 +120,6 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
// Need to retrieve the current principal, in order to know who "owns" this ACL // Need to retrieve the current principal, in order to know who "owns" this ACL
// (can be changed later on) // (can be changed later on)
Authentication auth = this.securityContextHolderStrategy.getContext().getAuthentication(); Authentication auth = this.securityContextHolderStrategy.getContext().getAuthentication();
Assert.isTrue(auth != null, "Authentication required");
PrincipalSid sid = new PrincipalSid(auth); PrincipalSid sid = new PrincipalSid(auth);
// Create the acl_object_identity row // Create the acl_object_identity row
@@ -158,12 +155,9 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
Assert.isTrue(entry_ instanceof AccessControlEntryImpl, "Unknown ACE class"); Assert.isTrue(entry_ instanceof AccessControlEntryImpl, "Unknown ACE class");
AccessControlEntryImpl entry = (AccessControlEntryImpl) entry_; AccessControlEntryImpl entry = (AccessControlEntryImpl) entry_;
Assert.state(acl.getId() != null, "ACL ID cannot be null");
stmt.setLong(1, (Long) acl.getId()); stmt.setLong(1, (Long) acl.getId());
stmt.setInt(2, i); stmt.setInt(2, i);
Long sidPrimaryKey = createOrRetrieveSidPrimaryKey(entry.getSid(), true); stmt.setLong(3, createOrRetrieveSidPrimaryKey(entry.getSid(), true));
Assert.state(sidPrimaryKey != null, "SID primary key cannot be null");
stmt.setLong(3, sidPrimaryKey);
stmt.setInt(4, entry.getPermission().getMask()); stmt.setInt(4, entry.getPermission().getMask());
stmt.setBoolean(5, entry.isGranting()); stmt.setBoolean(5, entry.isGranting());
stmt.setBoolean(6, entry.isAuditSuccess()); stmt.setBoolean(6, entry.isAuditSuccess());
@@ -195,14 +189,11 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
* @param allowCreate true if creation is permitted if not found * @param allowCreate true if creation is permitted if not found
* @return the primary key or null if not found * @return the primary key or null if not found
*/ */
protected @Nullable Long createOrRetrieveClassPrimaryKey(String type, boolean allowCreate, Class idType) { protected Long createOrRetrieveClassPrimaryKey(String type, boolean allowCreate, Class idType) {
List<@Nullable Long> classIds = this.jdbcOperations.queryForList(this.selectClassPrimaryKey, Long.class, type); List<Long> classIds = this.jdbcOperations.queryForList(this.selectClassPrimaryKey, Long.class, type);
if (!classIds.isEmpty()) { if (!classIds.isEmpty()) {
Long result = classIds.get(0); return classIds.get(0);
if (result != null) {
return result;
}
} }
if (allowCreate) { if (allowCreate) {
@@ -213,9 +204,7 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
this.jdbcOperations.update(this.insertClass, type, idType.getCanonicalName()); this.jdbcOperations.update(this.insertClass, type, idType.getCanonicalName());
} }
Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), "Transaction must be running"); Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), "Transaction must be running");
Long result = this.jdbcOperations.queryForObject(this.classIdentityQuery, Long.class); return this.jdbcOperations.queryForObject(this.classIdentityQuery, Long.class);
Assert.state(result != null, "Failed to retrieve class primary key");
return result;
} }
return null; return null;
@@ -230,7 +219,7 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
* @throws IllegalArgumentException if the <tt>Sid</tt> is not a recognized * @throws IllegalArgumentException if the <tt>Sid</tt> is not a recognized
* implementation. * implementation.
*/ */
protected @Nullable Long createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) { protected Long createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) {
Assert.notNull(sid, "Sid required"); Assert.notNull(sid, "Sid required");
if (sid instanceof PrincipalSid) { if (sid instanceof PrincipalSid) {
String sidName = ((PrincipalSid) sid).getPrincipal(); String sidName = ((PrincipalSid) sid).getPrincipal();
@@ -251,22 +240,16 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
* @param allowCreate true if creation is permitted if not found * @param allowCreate true if creation is permitted if not found
* @return the primary key or null if not found * @return the primary key or null if not found
*/ */
protected @Nullable Long createOrRetrieveSidPrimaryKey(String sidName, boolean sidIsPrincipal, protected Long createOrRetrieveSidPrimaryKey(String sidName, boolean sidIsPrincipal, boolean allowCreate) {
boolean allowCreate) { List<Long> sidIds = this.jdbcOperations.queryForList(this.selectSidPrimaryKey, Long.class, sidIsPrincipal,
List<@Nullable Long> sidIds = this.jdbcOperations.queryForList(this.selectSidPrimaryKey, Long.class, sidName);
sidIsPrincipal, sidName);
if (!sidIds.isEmpty()) { if (!sidIds.isEmpty()) {
Long result = sidIds.get(0); return sidIds.get(0);
if (result != null) {
return result;
}
} }
if (allowCreate) { if (allowCreate) {
this.jdbcOperations.update(this.insertSid, sidIsPrincipal, sidName); this.jdbcOperations.update(this.insertSid, sidIsPrincipal, sidName);
Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), "Transaction must be running"); Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), "Transaction must be running");
Long result = this.jdbcOperations.queryForObject(this.sidIdentityQuery, Long.class); return this.jdbcOperations.queryForObject(this.sidIdentityQuery, Long.class);
Assert.state(result != null, "Failed to retrieve sid primary key");
return result;
} }
return null; return null;
} }
@@ -296,9 +279,6 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
} }
Long oidPrimaryKey = retrieveObjectIdentityPrimaryKey(objectIdentity); Long oidPrimaryKey = retrieveObjectIdentityPrimaryKey(objectIdentity);
if (oidPrimaryKey == null) {
throw new NotFoundException("Object identity not found: " + objectIdentity);
}
// Delete this ACL's ACEs in the acl_entry table // Delete this ACL's ACEs in the acl_entry table
deleteEntries(oidPrimaryKey); deleteEntries(oidPrimaryKey);
@@ -339,11 +319,10 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
* @param oid to find * @param oid to find
* @return the object identity or null if not found * @return the object identity or null if not found
*/ */
protected @Nullable Long retrieveObjectIdentityPrimaryKey(ObjectIdentity oid) { protected Long retrieveObjectIdentityPrimaryKey(ObjectIdentity oid) {
try { try {
Long result = this.jdbcOperations.queryForObject(this.selectObjectIdentityPrimaryKey, Long.class, return this.jdbcOperations.queryForObject(this.selectObjectIdentityPrimaryKey, Long.class, oid.getType(),
oid.getType(), oid.getIdentifier().toString()); oid.getIdentifier().toString());
return result;
} }
catch (DataAccessException notFound) { catch (DataAccessException notFound) {
return null; return null;
@@ -361,11 +340,7 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
Assert.notNull(acl.getId(), "Object Identity doesn't provide an identifier"); Assert.notNull(acl.getId(), "Object Identity doesn't provide an identifier");
// Delete this ACL's ACEs in the acl_entry table // Delete this ACL's ACEs in the acl_entry table
Long oidPrimaryKey = retrieveObjectIdentityPrimaryKey(acl.getObjectIdentity()); deleteEntries(retrieveObjectIdentityPrimaryKey(acl.getObjectIdentity()));
if (oidPrimaryKey == null) {
throw new NotFoundException("Object identity not found for ACL: " + acl.getObjectIdentity());
}
deleteEntries(oidPrimaryKey);
// Create this ACL's ACEs in the acl_entry table // Create this ACL's ACEs in the acl_entry table
createEntries(acl); createEntries(acl);
@@ -496,7 +471,7 @@ public class JdbcMutableAclService extends JdbcAclService implements MutableAclS
this.insertClass = DEFAULT_INSERT_INTO_ACL_CLASS_WITH_ID; this.insertClass = DEFAULT_INSERT_INTO_ACL_CLASS_WITH_ID;
} }
else { else {
log.debug("Insert class statement has already been overridden, so not overriding the default"); log.debug("Insert class statement has already been overridden, so not overridding the default");
} }
} }
} }
@@ -19,8 +19,6 @@ package org.springframework.security.acls.jdbc;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.jspecify.annotations.Nullable;
import org.springframework.security.acls.model.Acl; import org.springframework.security.acls.model.Acl;
import org.springframework.security.acls.model.NotFoundException; import org.springframework.security.acls.model.NotFoundException;
import org.springframework.security.acls.model.ObjectIdentity; import org.springframework.security.acls.model.ObjectIdentity;
@@ -44,6 +42,6 @@ public interface LookupStrategy {
* {@link NotFoundException}, as a chain of {@link LookupStrategy}s may be used to * {@link NotFoundException}, as a chain of {@link LookupStrategy}s may be used to
* automatically create entries if required) * automatically create entries if required)
*/ */
Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, @Nullable List<Sid> sids); Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, List<Sid> sids);
} }
@@ -17,7 +17,4 @@
/** /**
* JDBC-based persistence of ACL information * JDBC-based persistence of ACL information
*/ */
@NullMarked
package org.springframework.security.acls.jdbc; package org.springframework.security.acls.jdbc;
import org.jspecify.annotations.NullMarked;
@@ -18,8 +18,6 @@ package org.springframework.security.acls.model;
import java.io.Serializable; import java.io.Serializable;
import org.jspecify.annotations.Nullable;
/** /**
* Represents an individual permission assignment within an {@link Acl}. * Represents an individual permission assignment within an {@link Acl}.
* *
@@ -38,7 +36,7 @@ public interface AccessControlEntry extends Serializable {
* Obtains an identifier that represents this ACE. * Obtains an identifier that represents this ACE.
* @return the identifier, or <code>null</code> if unsaved * @return the identifier, or <code>null</code> if unsaved
*/ */
@Nullable Serializable getId(); Serializable getId();
Permission getPermission(); Permission getPermission();
@@ -19,8 +19,6 @@ package org.springframework.security.acls.model;
import java.io.Serializable; import java.io.Serializable;
import java.util.List; import java.util.List;
import org.jspecify.annotations.Nullable;
/** /**
* Represents an access control list (ACL) for a domain object. * Represents an access control list (ACL) for a domain object.
* *
@@ -84,7 +82,7 @@ public interface Acl extends Serializable {
* @return the owner (may be <tt>null</tt> if the implementation does not use * @return the owner (may be <tt>null</tt> if the implementation does not use
* ownership concepts) * ownership concepts)
*/ */
@Nullable Sid getOwner(); Sid getOwner();
/** /**
* A domain object may have a parent for the purpose of ACL inheritance. If there is a * A domain object may have a parent for the purpose of ACL inheritance. If there is a
@@ -105,7 +103,7 @@ public interface Acl extends Serializable {
* @return the parent <tt>Acl</tt> (may be <tt>null</tt> if this <tt>Acl</tt> does not * @return the parent <tt>Acl</tt> (may be <tt>null</tt> if this <tt>Acl</tt> does not
* have a parent) * have a parent)
*/ */
@Nullable Acl getParentAcl(); Acl getParentAcl();
/** /**
* Indicates whether the ACL entries from the {@link #getParentAcl()} should flow down * Indicates whether the ACL entries from the {@link #getParentAcl()} should flow down
@@ -191,6 +189,6 @@ public interface Acl extends Serializable {
* @return <tt>true</tt> if every passed <tt>Sid</tt> is represented by this * @return <tt>true</tt> if every passed <tt>Sid</tt> is represented by this
* <tt>Acl</tt> instance * <tt>Acl</tt> instance
*/ */
boolean isSidLoaded(@Nullable List<Sid> sids); boolean isSidLoaded(List<Sid> sids);
} }
@@ -18,8 +18,6 @@ package org.springframework.security.acls.model;
import java.io.Serializable; import java.io.Serializable;
import org.jspecify.annotations.Nullable;
import org.springframework.security.acls.jdbc.JdbcAclService; import org.springframework.security.acls.jdbc.JdbcAclService;
/** /**
@@ -33,9 +31,9 @@ public interface AclCache {
void evictFromCache(ObjectIdentity objectIdentity); void evictFromCache(ObjectIdentity objectIdentity);
@Nullable MutableAcl getFromCache(ObjectIdentity objectIdentity); MutableAcl getFromCache(ObjectIdentity objectIdentity);
@Nullable MutableAcl getFromCache(Serializable pk); MutableAcl getFromCache(Serializable pk);
void putInCache(MutableAcl acl); void putInCache(MutableAcl acl);
@@ -19,8 +19,6 @@ package org.springframework.security.acls.model;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.jspecify.annotations.Nullable;
/** /**
* Provides retrieval of {@link Acl} instances. * Provides retrieval of {@link Acl} instances.
* *
@@ -34,7 +32,7 @@ public interface AclService {
* @param parentIdentity to locate children of * @param parentIdentity to locate children of
* @return the children (or <tt>null</tt> if none were found) * @return the children (or <tt>null</tt> if none were found)
*/ */
@Nullable List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity); List<ObjectIdentity> findChildren(ObjectIdentity parentIdentity);
/** /**
* Same as {@link #readAclsById(List)} except it returns only a single Acl. * Same as {@link #readAclsById(List)} except it returns only a single Acl.
@@ -61,7 +59,7 @@ public interface AclService {
* @throws NotFoundException if an {@link Acl} was not found for the requested * @throws NotFoundException if an {@link Acl} was not found for the requested
* {@link ObjectIdentity} * {@link ObjectIdentity}
*/ */
Acl readAclById(ObjectIdentity object, @Nullable List<Sid> sids) throws NotFoundException; Acl readAclById(ObjectIdentity object, List<Sid> sids) throws NotFoundException;
/** /**
* Obtains all the <tt>Acl</tt>s that apply for the passed <tt>Object</tt>s. * Obtains all the <tt>Acl</tt>s that apply for the passed <tt>Object</tt>s.
@@ -100,7 +98,6 @@ public interface AclService {
* @throws NotFoundException if an {@link Acl} was not found for each requested * @throws NotFoundException if an {@link Acl} was not found for each requested
* {@link ObjectIdentity} * {@link ObjectIdentity}
*/ */
Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, @Nullable List<Sid> sids) Map<ObjectIdentity, Acl> readAclsById(List<ObjectIdentity> objects, List<Sid> sids) throws NotFoundException;
throws NotFoundException;
} }
@@ -18,7 +18,4 @@
* Interfaces and shared classes to manage access control lists (ACLs) for domain object * Interfaces and shared classes to manage access control lists (ACLs) for domain object
* instances. * instances.
*/ */
@NullMarked
package org.springframework.security.acls.model; package org.springframework.security.acls.model;
import org.jspecify.annotations.NullMarked;
@@ -24,7 +24,4 @@
* older and more verbose attribute/voter/after-invocation approach from versions before * older and more verbose attribute/voter/after-invocation approach from versions before
* Spring Security 3.0. * Spring Security 3.0.
*/ */
@NullMarked
package org.springframework.security.acls; package org.springframework.security.acls;
import org.jspecify.annotations.NullMarked;
@@ -1,2 +0,0 @@
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.security.acls.aot.hint.AclRuntimeHints
@@ -478,7 +478,6 @@ public class AclImplTests {
} }
@Test @Test
@SuppressWarnings("unchecked")
public void hashCodeWithoutStackOverFlow() throws Exception { public void hashCodeWithoutStackOverFlow() throws Exception {
Sid sid = new PrincipalSid("pSid"); Sid sid = new PrincipalSid("pSid");
ObjectIdentity oid = new ObjectIdentityImpl("type", 1); ObjectIdentity oid = new ObjectIdentityImpl("type", 1);
@@ -29,7 +29,6 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
@@ -47,6 +46,7 @@ import org.springframework.security.acls.model.Sid;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
@@ -109,8 +109,7 @@ public class JdbcAclServiceTests {
List<ObjectIdentity> result = new ArrayList<>(); List<ObjectIdentity> result = new ArrayList<>();
result.add(new ObjectIdentityImpl(Object.class, "5577")); result.add(new ObjectIdentityImpl(Object.class, "5577"));
Object[] args = { "1", "org.springframework.security.acls.jdbc.JdbcAclServiceTests$MockLongIdDomainObject" }; Object[] args = { "1", "org.springframework.security.acls.jdbc.JdbcAclServiceTests$MockLongIdDomainObject" };
given(this.jdbcOperations.query(anyString(), ArgumentMatchers.<RowMapper<ObjectIdentity>>any(), eq(args))) given(this.jdbcOperations.query(anyString(), any(RowMapper.class), eq(args))).willReturn(result);
.willReturn(result);
ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockLongIdDomainObject.class, 1L); ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockLongIdDomainObject.class, 1L);
List<ObjectIdentity> objectIdentities = this.aclService.findChildren(objectIdentity); List<ObjectIdentity> objectIdentities = this.aclService.findChildren(objectIdentity);
assertThat(objectIdentities).hasSize(1); assertThat(objectIdentities).hasSize(1);
@@ -80,10 +80,11 @@ public class SpringCacheBasedAclCacheTests {
assertThatIllegalArgumentException().isThrownBy(() -> new SpringCacheBasedAclCache(null, null, null)); assertThatIllegalArgumentException().isThrownBy(() -> new SpringCacheBasedAclCache(null, null, null));
} }
@SuppressWarnings("rawtypes")
@Test @Test
public void cacheOperationsAclWithoutParent() { public void cacheOperationsAclWithoutParent() {
Cache cache = getCache(); Cache cache = getCache();
Map<?, ?> realCache = (Map<?, ?>) cache.getNativeCache(); Map realCache = (Map) cache.getNativeCache();
ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100L); ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100L);
AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl( AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"), new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"),
@@ -115,10 +116,11 @@ public class SpringCacheBasedAclCacheTests {
assertThat(realCache).isEmpty(); assertThat(realCache).isEmpty();
} }
@SuppressWarnings("rawtypes")
@Test @Test
public void cacheOperationsAclWithParent() throws Exception { public void cacheOperationsAclWithParent() throws Exception {
Cache cache = getCache(); Cache cache = getCache();
Map<?, ?> realCache = (Map<?, ?>) cache.getNativeCache(); Map realCache = (Map) cache.getNativeCache();
Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_GENERAL"); Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_GENERAL");
auth.setAuthenticated(true); auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth); SecurityContextHolder.getContext().setAuthentication(auth);
+4 -8
View File
@@ -1,17 +1,13 @@
apply plugin: 'io.spring.convention.spring-module' apply plugin: 'io.spring.convention.spring-module'
apply plugin: 'io.freefair.aspectj' apply plugin: 'io.freefair.aspectj'
apply plugin: 'javadoc-warnings-error'
apply plugin: 'compile-warnings-error'
compileAspectj { compileAspectj {
sourceCompatibility = "17" sourceCompatibility "17"
targetCompatibility = "17" targetCompatibility "17"
ajcOptions.compilerArgs += ['-Xlint:ignore']
} }
compileTestAspectj { compileTestAspectj {
sourceCompatibility = "17" sourceCompatibility "17"
targetCompatibility = "17" targetCompatibility "17"
ajcOptions.compilerArgs += ['-Xlint:ignore']
} }
dependencies { dependencies {
-1
View File
@@ -1,7 +1,6 @@
import io.spring.gradle.convention.SpringModulePlugin import io.spring.gradle.convention.SpringModulePlugin
apply plugin: 'io.spring.convention.bom' apply plugin: 'io.spring.convention.bom'
apply plugin: 'compile-warnings-error'
dependencies { dependencies {
constraints { constraints {
+37 -12
View File
@@ -1,7 +1,5 @@
import io.spring.gradle.IncludeRepoTask import io.spring.gradle.IncludeRepoTask
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import trang.RncToXsd import trang.RncToXsd
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
buildscript { buildscript {
dependencies { dependencies {
@@ -12,7 +10,7 @@ buildscript {
classpath libs.com.netflix.nebula.nebula.project.plugin classpath libs.com.netflix.nebula.nebula.project.plugin
} }
repositories { repositories {
maven { url='https://plugins.gradle.org/m2/' } maven { url 'https://plugins.gradle.org/m2/' }
} }
} }
@@ -37,30 +35,57 @@ ext.milestoneBuild = !(snapshotBuild || releaseBuild)
repositories { repositories {
mavenCentral() mavenCentral()
maven { url = "https://repo.spring.io/milestone" } maven { url "https://repo.spring.io/milestone" }
} }
springRelease { springRelease {
weekOfMonth = 3 weekOfMonth = 3
dayOfWeek = 1 dayOfWeek = 1
referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html" referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html"
apiDocUrl = "https://docs.spring.io/spring-security/reference/{version}/api/java/index.html" apiDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/api/"
replaceSnapshotVersionInReferenceDocUrl = true replaceSnapshotVersionInReferenceDocUrl = true
} }
def toolchainVersion() {
if (project.hasProperty('testToolchain')) {
return project.property('testToolchain').toString().toInteger()
}
return 17
}
subprojects {
java {
toolchain {
languageVersion = JavaLanguageVersion.of(toolchainVersion())
}
}
kotlin {
jvmToolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
options.release.set(17)
}
}
allprojects { allprojects {
if (!['spring-security-bom', 'spring-security-docs'].contains(project.name)) { if (!['spring-security-bom', 'spring-security-docs'].contains(project.name)) {
apply plugin: 'io.spring.javaformat' apply plugin: 'io.spring.javaformat'
apply plugin: 'checkstyle' apply plugin: 'checkstyle'
pluginManager.withPlugin("io.spring.convention.checkstyle") { pluginManager.withPlugin("io.spring.convention.checkstyle", { plugin ->
dependencies { configure(plugin) {
checkstyle libs.io.spring.javaformat.spring.javaformat.checkstyle dependencies {
checkstyle libs.io.spring.javaformat.spring.javaformat.checkstyle
}
checkstyle {
toolVersion = '8.34'
}
} }
checkstyle { })
toolVersion = '8.34'
}
}
if (project.name.contains('sample')) { if (project.name.contains('sample')) {
tasks.whenTaskAdded { task -> tasks.whenTaskAdded { task ->
+1 -1
View File
@@ -12,7 +12,7 @@ java {
repositories { repositories {
gradlePluginPortal() gradlePluginPortal()
mavenCentral() mavenCentral()
maven { url = 'https://repo.spring.io/snapshot' } maven { url 'https://repo.spring.io/snapshot' }
} }
sourceSets { sourceSets {
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+240
View File
@@ -0,0 +1,240 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original 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.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+91
View File
@@ -0,0 +1,91 @@
@rem
@rem Copyright 2004-present the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@@ -1,11 +0,0 @@
import org.gradle.api.tasks.compile.JavaCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
tasks.withType(JavaCompile) {
options.compilerArgs += "-Werror"
}
tasks.withType(KotlinCompile) {
kotlinOptions.allWarningsAsErrors = true
}
@@ -81,8 +81,8 @@ class ArtifactoryPlugin implements Plugin<Project> {
repository { repository {
repoKey = isSnapshot ? snapshotRepository : isMilestone ? milestoneRepository : releaseRepository repoKey = isSnapshot ? snapshotRepository : isMilestone ? milestoneRepository : releaseRepository
if(project.hasProperty('artifactoryUsername')) { if(project.hasProperty('artifactoryUsername')) {
username = project.artifactoryUsername username = artifactoryUsername
password = project.artifactoryPassword password = artifactoryPassword
} }
} }
} }
@@ -0,0 +1,82 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.spring.gradle.convention
import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.bundling.Zip
import org.gradle.api.Plugin
import org.gradle.api.Project
public class DeployDocsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
project.getPluginManager().apply('org.hidetake.ssh')
project.ssh.settings {
knownHosts = allowAnyHosts
}
project.remotes {
docs {
role 'docs'
if (project.hasProperty('deployDocsHost')) {
host = project.findProperty('deployDocsHost')
} else {
host = 'docs.af.pivotal.io'
}
retryCount = 5 // retry 5 times (default is 0)
retryWaitSec = 10 // wait 10 seconds between retries (default is 0)
user = project.findProperty('deployDocsSshUsername')
if (project.hasProperty('deployDocsSshKeyPath')) {
identity = project.file(project.findProperty('deployDocsSshKeyPath'))
} else if (project.hasProperty('deployDocsSshKey')) {
identity = project.findProperty('deployDocsSshKey')
}
if(project.hasProperty('deployDocsSshPassphrase')) {
passphrase = project.findProperty('deployDocsSshPassphrase')
}
}
}
project.task('deployDocs') {
dependsOn 'docsZip'
doFirst {
project.ssh.run {
session(project.remotes.docs) {
def now = System.currentTimeMillis()
def name = project.rootProject.name
def version = project.rootProject.version
def tempPath = "/tmp/${name}-${now}-docs/".replaceAll(' ', '_')
execute "mkdir -p $tempPath"
project.tasks.docsZip.outputs.each { o ->
put from: o.files, into: tempPath
}
execute "unzip $tempPath*.zip -d $tempPath"
def extractPath = "/var/www/domains/spring.io/docs/htdocs/autorepo/docs/${name}/${version}/"
execute "rm -rf $extractPath"
execute "mkdir -p $extractPath"
execute "mv $tempPath/docs/* $extractPath"
execute "chmod -R g+w $extractPath"
}
}
}
}
}
}
@@ -17,6 +17,7 @@ public class DocsPlugin implements Plugin<Project> {
PluginManager pluginManager = project.getPluginManager(); PluginManager pluginManager = project.getPluginManager();
pluginManager.apply(BasePlugin); pluginManager.apply(BasePlugin);
pluginManager.apply(DeployDocsPlugin);
pluginManager.apply(JavadocApiPlugin); pluginManager.apply(JavadocApiPlugin);
Task docsZip = project.tasks.create('docsZip', Zip) { Task docsZip = project.tasks.create('docsZip', Zip) {
@@ -31,12 +32,12 @@ public class DocsPlugin implements Plugin<Project> {
into 'api' into 'api'
} }
into 'docs' into 'docs'
duplicatesStrategy = 'exclude' duplicatesStrategy 'exclude'
} }
Task docs = project.tasks.create("docs") { Task docs = project.tasks.create("docs") {
group = 'Documentation' group = 'Documentation'
description = 'An aggregator task to generate all the documentation' description 'An aggregator task to generate all the documentation'
dependsOn docsZip dependsOn docsZip
} }
project.tasks.assemble.dependsOn docs project.tasks.assemble.dependsOn docs
@@ -90,7 +90,7 @@ public class IntegrationTestPlugin implements Plugin<Project> {
project.plugins.withType(IdeaPlugin) { project.plugins.withType(IdeaPlugin) {
project.idea { project.idea {
module { module {
testSources.from(project.file('src/integration-test/java')) testSourceDirs += project.file('src/integration-test/java')
scopes.TEST.plus += [ project.configurations.integrationTestCompileClasspath ] scopes.TEST.plus += [ project.configurations.integrationTestCompileClasspath ]
} }
} }
@@ -105,7 +105,7 @@ public class IntegrationTestPlugin implements Plugin<Project> {
project.plugins.withType(IdeaPlugin) { project.plugins.withType(IdeaPlugin) {
project.idea { project.idea {
module { module {
testSources.from(project.file('src/integration-test/groovy')) testSourceDirs += project.file('src/integration-test/groovy')
} }
} }
} }
@@ -34,7 +34,7 @@ class JacocoPlugin implements Plugin<Project> {
project.tasks.check.dependsOn project.tasks.jacocoTestReport project.tasks.check.dependsOn project.tasks.jacocoTestReport
project.jacoco { project.jacoco {
toolVersion = '0.8.14' toolVersion = '0.8.9'
} }
} }
} }
@@ -26,7 +26,7 @@ import org.gradle.api.Action;
import org.gradle.api.JavaVersion import org.gradle.api.JavaVersion
import org.gradle.api.Plugin; import org.gradle.api.Plugin;
import org.gradle.api.Project; import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.plugins.JavaPluginConvention;
import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.javadoc.Javadoc; import org.gradle.api.tasks.javadoc.Javadoc;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -71,7 +71,7 @@ public class JavadocApiPlugin implements Plugin<Project> {
} }
api.setMaxMemory("1024m"); api.setMaxMemory("1024m");
api.setDestinationDir(project.layout.getBuildDirectory().dir("api").get().getAsFile()); api.setDestinationDir(new File(project.getBuildDir(), "api"));
project.getPluginManager().apply("io.spring.convention.javadoc-options"); project.getPluginManager().apply("io.spring.convention.javadoc-options");
} }
@@ -99,7 +99,7 @@ public class JavadocApiPlugin implements Plugin<Project> {
public void execute(SpringModulePlugin plugin) { public void execute(SpringModulePlugin plugin) {
logger.info("Added sources for {}", project); logger.info("Added sources for {}", project);
JavaPluginExtension java = project.getExtensions().getByType(JavaPluginExtension.class); JavaPluginConvention java = project.getConvention().getPlugin(JavaPluginConvention.class);
SourceSet mainSourceSet = java.getSourceSets().getByName("main"); SourceSet mainSourceSet = java.getSourceSets().getByName("main");
api.setSource(api.getSource().plus(mainSourceSet.getAllJava())); api.setSource(api.getSource().plus(mainSourceSet.getAllJava()));
@@ -40,7 +40,7 @@ public class SchemaZipPlugin implements Plugin<Project> {
throw new IllegalStateException("Could not find schema file for resource name " + schemaResourceName + " in src/main/resources") throw new IllegalStateException("Could not find schema file for resource name " + schemaResourceName + " in src/main/resources")
} }
schemaZip.into (shortName) { schemaZip.into (shortName) {
duplicatesStrategy = 'exclude' duplicatesStrategy 'exclude'
from xsdFile.path from xsdFile.path
} }
versionlessXsd.getInputFiles().from(xsdFile.path) versionlessXsd.getInputFiles().from(xsdFile.path)
@@ -35,7 +35,6 @@ class SpringModulePlugin extends AbstractSpringJavaPlugin {
pluginManager.apply(SpringMavenPlugin.class); pluginManager.apply(SpringMavenPlugin.class);
pluginManager.apply(CheckClasspathForProhibitedDependenciesPlugin.class); pluginManager.apply(CheckClasspathForProhibitedDependenciesPlugin.class);
pluginManager.apply("io.spring.convention.jacoco"); pluginManager.apply("io.spring.convention.jacoco");
pluginManager.apply("java-toolchain");
def deployArtifacts = project.task("deployArtifacts") def deployArtifacts = project.task("deployArtifacts")
deployArtifacts.group = 'Deploy tasks' deployArtifacts.group = 'Deploy tasks'
@@ -1,36 +0,0 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
def toolchainVersion() {
if (project.hasProperty('testToolchain')) {
return project.property('testToolchain').toString().toInteger()
}
return 25
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(toolchainVersion())
}
}
tasks.withType(JavaCompile).configureEach {
options.encoding = "UTF-8"
options.compilerArgs.add("-parameters")
options.release = 17
}
pluginManager.withPlugin("org.jetbrains.kotlin.jvm") {
kotlin {
jvmToolchain {
languageVersion = JavaLanguageVersion.of(toolchainVersion())
}
}
tasks.withType(KotlinCompile).configureEach {
compilerOptions {
javaParameters = true
jvmTarget.set(JvmTarget.JVM_17)
}
}
}
@@ -1,7 +0,0 @@
import org.gradle.api.tasks.javadoc.Javadoc
project.tasks.withType(Javadoc).configureEach {
options.addBooleanOption('Werror', true)
// temporarily disable missing to get build to pass with JDK 25
options.addStringOption('Xdoclint:all,-missing')
}
@@ -1,30 +0,0 @@
import org.gradle.api.tasks.compile.JavaCompile
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
/**
* We need to compile with JDK 25 for nullability support, but using JDK 25 means that our tests will fail due to the
* <a href="https://docs.oracle.com/en/java/javase/25/security/security-manager-is-permanently-disabled.html">removal
* of the Java Security Manager</a>. For example, in JDK 25 {@code Subject.getSubject(AccessControlContext)} throws an
* {@code UnsupportedOperationException}.
*
* To resolve this, we must migrate tests to use the new APIs (e.g. {@code Subject.current()}) but those APIs are not
* available in the JDK 17 source, so compiling with JDK 25 and release 17 fails. The plugin overrides the test
* compilation to use release 25.
*
* @see <a href="https://docs.oracle.com/en/java/javase/25/security/security-manager-is-permanently-disabled.html">The
* Security Manager Is Permanently Disabled</a>
* @see <a href="https://inside.java/2024/07/08/quality-heads-up/">Quality Outreach Heads-up - JDK 23: Re-Specified
* Subject.getSubject API</a>
*/
tasks.withType(JavaCompile).configureEach { task ->
if (task.name == 'compileTestJava' || task.name == 'compileIntegrationTestJava') {
task.options.release.set(25)
}
}
tasks.withType(KotlinCompile).configureEach { task ->
if (task.name == 'compileTestKotlin' || task.name == 'compileIntegrationTestKotlin') {
task.kotlinOptions.jvmTarget = '25'
}
}
+11 -1
View File
@@ -30,6 +30,16 @@ ossrh: {
} }
} }
}, },
docs: {
stage('Deploy Docs') {
node {
checkout scm
withCredentials([file(credentialsId: 'docs.spring.io-jenkins_private_ssh_key', variable: 'DEPLOY_SSH_KEY')]) {
sh "./gradlew deployDocs -PdeployDocsSshKeyPath=$DEPLOY_SSH_KEY -PdeployDocsSshUsername=$SPRING_DOCS_USERNAME --refresh-dependencies --no-daemon --stacktrace"
}
}
}
},
schema: { schema: {
stage('Deploy Schema') { stage('Deploy Schema') {
node { node {
@@ -39,4 +49,4 @@ schema: {
} }
} }
} }
} }
-2
View File
@@ -1,7 +1,5 @@
plugins { plugins {
id 'security-nullability' id 'security-nullability'
id 'javadoc-warnings-error'
id 'compile-warnings-error'
} }
apply plugin: 'io.spring.convention.spring-module' apply plugin: 'io.spring.convention.spring-module'
@@ -24,6 +24,7 @@ import org.apache.commons.logging.LogFactory;
import org.apereo.cas.client.validation.Assertion; import org.apereo.cas.client.validation.Assertion;
import org.apereo.cas.client.validation.TicketValidationException; import org.apereo.cas.client.validation.TicketValidationException;
import org.apereo.cas.client.validation.TicketValidator; import org.apereo.cas.client.validation.TicketValidator;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
@@ -165,6 +166,7 @@ public class CasAuthenticationProvider implements AuthenticationProvider, Initia
* @param authentication * @param authentication
* @return * @return
*/ */
@NullUnmarked
private @Nullable String getServiceUrl(Authentication authentication) { private @Nullable String getServiceUrl(Authentication authentication) {
String serviceUrl; String serviceUrl;
if (authentication.getDetails() instanceof ServiceAuthenticationDetails) { if (authentication.getDetails() instanceof ServiceAuthenticationDetails) {
@@ -17,7 +17,4 @@
/** /**
* Jackson 3+ serialization support for CAS. * Jackson 3+ serialization support for CAS.
*/ */
@NullMarked
package org.springframework.security.cas.jackson; package org.springframework.security.cas.jackson;
import org.jspecify.annotations.NullMarked;
@@ -91,8 +91,7 @@ public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, In
*/ */
protected String createServiceUrl(HttpServletRequest request, HttpServletResponse response) { protected String createServiceUrl(HttpServletRequest request, HttpServletResponse response) {
return WebUtils.constructServiceUrl(null, response, this.serviceProperties.getService(), null, return WebUtils.constructServiceUrl(null, response, this.serviceProperties.getService(), null,
this.serviceProperties.getServiceParameter(), this.serviceProperties.getArtifactParameter(), this.serviceProperties.getArtifactParameter(), this.encodeServiceUrlWithSessionId);
this.encodeServiceUrlWithSessionId);
} }
/** /**
@@ -326,7 +326,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
/** /**
* Use this {@code RequestMatcher} to match proxy receptor requests. Without setting * Use this {@code RequestMatcher} to match proxy receptor requests. Without setting
* this matcher, {@link CasAuthenticationFilter} will not capture any proxy receptor * this matcher, {@link CasAuthenticationFilter} will not capture any proxy receptor
* requests. * requets.
* @param proxyReceptorMatcher the {@link RequestMatcher} to use * @param proxyReceptorMatcher the {@link RequestMatcher} to use
* @since 6.5 * @since 6.5
*/ */
@@ -383,8 +383,8 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
} }
/** /**
* Indicates if the request is eligible to process a service ticket. This method * Indicates if the request is elgible to process a service ticket. This method exists
* exists for readability. * for readability.
* @param request * @param request
* @param response * @param response
* @return * @return
@@ -396,7 +396,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
} }
/** /**
* Indicates if the request is eligible to process a proxy ticket. * Indicates if the request is elgible to process a proxy ticket.
* @param request * @param request
* @return * @return
*/ */
@@ -419,7 +419,7 @@ public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFil
} }
/** /**
* Indicates if the request is eligible to be processed as the proxy receptor. * Indicates if the request is elgible to be processed as the proxy receptor.
* @param request * @param request
* @return * @return
*/ */
@@ -34,7 +34,6 @@ import org.springframework.util.Assert;
* and using the current URL minus the artifact and the corresponding value. * and using the current URL minus the artifact and the corresponding value.
* *
* @author Rob Winch * @author Rob Winch
* @author Ngoc Nhan
*/ */
final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails
implements ServiceAuthenticationDetails { implements ServiceAuthenticationDetails {
@@ -75,9 +74,10 @@ final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails
if (this == obj) { if (this == obj) {
return true; return true;
} }
if (!super.equals(obj) || !(obj instanceof DefaultServiceAuthenticationDetails that)) { if (!super.equals(obj) || !(obj instanceof DefaultServiceAuthenticationDetails)) {
return false; return false;
} }
ServiceAuthenticationDetails that = (ServiceAuthenticationDetails) obj;
return this.serviceUrl.equals(that.getServiceUrl()); return this.serviceUrl.equals(that.getServiceUrl());
} }
@@ -101,11 +101,7 @@ final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails
/** /**
* If present, removes the artifactParameterName and the corresponding value from the * If present, removes the artifactParameterName and the corresponding value from the
* query String. * query String.
* @param request the current {@link HttpServletRequest} to obtain the * @param request
* {@link #getServiceUrl()} from.
* @param artifactPattern the {@link Pattern} that will be used to clean up the query
* string from containing the artifact name and value. This can be created using
* {@link #createArtifactPattern(String)}.
* @return the query String minus the artifactParameterName and the corresponding * @return the query String minus the artifactParameterName and the corresponding
* value. * value.
*/ */
@@ -115,7 +111,7 @@ final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails
return null; return null;
} }
String result = artifactPattern.matcher(query).replaceFirst(""); String result = artifactPattern.matcher(query).replaceFirst("");
if (result.isEmpty()) { if (result.length() == 0) {
return null; return null;
} }
// strip off the trailing & only if the artifact was the first query param // strip off the trailing & only if the artifact was the first query param
@@ -126,9 +122,8 @@ final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails
* Creates a {@link Pattern} that can be passed into the constructor. This allows the * Creates a {@link Pattern} that can be passed into the constructor. This allows the
* {@link Pattern} to be reused for every instance of * {@link Pattern} to be reused for every instance of
* {@link DefaultServiceAuthenticationDetails}. * {@link DefaultServiceAuthenticationDetails}.
* @param artifactParameterName the artifactParameterName that is removed from the * @param artifactParameterName
* current URL. The result becomes the service url. Cannot be null or an empty String. * @return
* @return a {@link Pattern}
*/ */
static Pattern createArtifactPattern(String artifactParameterName) { static Pattern createArtifactPattern(String artifactParameterName) {
Assert.hasLength(artifactParameterName, "artifactParameterName is expected to have a length"); Assert.hasLength(artifactParameterName, "artifactParameterName is expected to have a length");
+2 -5
View File
@@ -4,9 +4,6 @@ import trang.RncToXsd
apply plugin: 'io.spring.convention.spring-module' apply plugin: 'io.spring.convention.spring-module'
apply plugin: 'trang' apply plugin: 'trang'
apply plugin: 'security-kotlin' apply plugin: 'security-kotlin'
apply plugin: 'test-compile-target-jdk25'
apply plugin: 'compile-warnings-error'
apply plugin: 'javadoc-warnings-error'
configurations { configurations {
opensaml5 { opensaml5 {
@@ -147,14 +144,14 @@ tasks.named('processResources', ProcessResources).configure {
into 'org/springframework/security/config/' into 'org/springframework/security/config/'
} }
from(rncToXsd) { from(rncToXsd) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE duplicatesStrategy DuplicatesStrategy.EXCLUDE
into 'org/springframework/security/config/' into 'org/springframework/security/config/'
} }
} }
tasks.named('sourcesJar', Jar).configure { tasks.named('sourcesJar', Jar).configure {
from(rncToXsd) { from(rncToXsd) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE duplicatesStrategy DuplicatesStrategy.EXCLUDE
into 'org/springframework/security/config/' into 'org/springframework/security/config/'
} }
} }
@@ -31,7 +31,6 @@ import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebDriverException;
@@ -56,7 +55,6 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.FilterChainProxy; import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.DelegatingFilterProxy; import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@@ -69,7 +67,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* *
* @author Daniel Garnier-Moiroux * @author Daniel Garnier-Moiroux
*/ */
@Disabled @org.junit.jupiter.api.Disabled
class WebAuthnWebDriverTests { class WebAuthnWebDriverTests {
private String baseUrl; private String baseUrl;
@@ -84,8 +82,6 @@ class WebAuthnWebDriverTests {
private static final String PASSWORD = "password"; private static final String PASSWORD = "password";
private String authenticatorId = null;
@BeforeAll @BeforeAll
static void startChromeDriverService() throws Exception { static void startChromeDriverService() throws Exception {
driverService = new ChromeDriverService.Builder().usingAnyFreePort().build(); driverService = new ChromeDriverService.Builder().usingAnyFreePort().build();
@@ -148,7 +144,7 @@ class WebAuthnWebDriverTests {
@Test @Test
void loginWhenNoValidAuthenticatorCredentialsThenRejects() { void loginWhenNoValidAuthenticatorCredentialsThenRejects() {
createVirtualAuthenticator(true); createVirtualAuthenticator(true);
this.getAndWait("/", "/login"); this.driver.get(this.baseUrl);
this.driver.findElement(signinWithPasskeyButton()).click(); this.driver.findElement(signinWithPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?error")); await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?error"));
} }
@@ -157,7 +153,7 @@ class WebAuthnWebDriverTests {
void registerWhenNoLabelThenRejects() { void registerWhenNoLabelThenRejects() {
login(); login();
this.getAndWait("/webauthn/register"); this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(registerPasskeyButton()).click(); this.driver.findElement(registerPasskeyButton()).click();
assertHasAlertStartingWith("error", "Error: Passkey Label is required"); assertHasAlertStartingWith("error", "Error: Passkey Label is required");
@@ -167,7 +163,7 @@ class WebAuthnWebDriverTests {
void registerWhenAuthenticatorNoUserVerificationThenRejects() { void registerWhenAuthenticatorNoUserVerificationThenRejects() {
createVirtualAuthenticator(false); createVirtualAuthenticator(false);
login(); login();
this.getAndWait("/webauthn/register"); this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator"); this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
this.driver.findElement(registerPasskeyButton()).click(); this.driver.findElement(registerPasskeyButton()).click();
@@ -182,8 +178,7 @@ class WebAuthnWebDriverTests {
* <li>Step 1: Log in with username / password</li> * <li>Step 1: Log in with username / password</li>
* <li>Step 2: Register a credential from the virtual authenticator</li> * <li>Step 2: Register a credential from the virtual authenticator</li>
* <li>Step 3: Log out</li> * <li>Step 3: Log out</li>
* <li>Step 4: Log in with the authenticator (no allowCredentials)</li> * <li>Step 4: Log in with the authenticator</li>
* <li>Step 5: Log in again with the same authenticator (with allowCredentials)</li>
* </ul> * </ul>
*/ */
@Test @Test
@@ -195,7 +190,7 @@ class WebAuthnWebDriverTests {
login(); login();
// Step 2: register a credential from the virtual authenticator // Step 2: register a credential from the virtual authenticator
this.getAndWait("/webauthn/register"); this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator"); this.driver.findElement(passkeyLabel()).sendKeys("Virtual authenticator");
this.driver.findElement(registerPasskeyButton()).click(); this.driver.findElement(registerPasskeyButton()).click();
@@ -217,58 +212,9 @@ class WebAuthnWebDriverTests {
logout(); logout();
// Step 4: log in with the virtual authenticator // Step 4: log in with the virtual authenticator
this.getAndWait("/webauthn/register", "/login"); this.driver.get(this.baseUrl + "/webauthn/register");
this.driver.findElement(signinWithPasskeyButton()).click(); this.driver.findElement(signinWithPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?continue")); await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?continue"));
// Step 5: authenticate while being already logged in
// This simulates some use-cases with MFA. Since the user is already logged in,
// the "allowCredentials" property is populated
this.getAndWait("/login");
this.driver.findElement(signinWithPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/"));
}
@Test
void registerWhenAuthenticatorAlreadyRegisteredThenRejects() {
createVirtualAuthenticator(true);
login();
registerAuthenticator("Virtual authenticator");
// Cannot re-register the same authenticator because excludeCredentials
// is not empty and contains the given authenticator
this.driver.findElement(passkeyLabel()).sendKeys("Same authenticator");
this.driver.findElement(registerPasskeyButton()).click();
await(() -> assertHasAlertStartingWith("error", "Registration failed"));
}
@Test
void registerSecondAuthenticatorThenSucceeds() {
createVirtualAuthenticator(true);
login();
registerAuthenticator("Virtual authenticator");
this.getAndWait("/webauthn/register");
List<WebElement> passkeyRows = this.driver.findElements(passkeyTableRows());
assertThat(passkeyRows).hasSize(1)
.first()
.extracting((row) -> row.findElement(firstCell()))
.extracting(WebElement::getText)
.isEqualTo("Virtual authenticator");
// Create second authenticator and register
removeAuthenticator();
createVirtualAuthenticator(true);
registerAuthenticator("Second virtual authenticator");
this.getAndWait("/webauthn/register");
passkeyRows = this.driver.findElements(passkeyTableRows());
assertThat(passkeyRows).hasSize(2)
.extracting((row) -> row.findElement(firstCell()))
.extracting(WebElement::getText)
.contains("Second virtual authenticator");
} }
/** /**
@@ -285,14 +231,11 @@ class WebAuthnWebDriverTests {
* "https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/">https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/</a> * "https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/">https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/</a>
*/ */
private void createVirtualAuthenticator(boolean userIsVerified) { private void createVirtualAuthenticator(boolean userIsVerified) {
if (StringUtils.hasText(this.authenticatorId)) {
throw new IllegalStateException("Authenticator already exists, please remove it before re-creating one");
}
HasCdp cdpDriver = (HasCdp) this.driver; HasCdp cdpDriver = (HasCdp) this.driver;
cdpDriver.executeCdpCommand("WebAuthn.enable", Map.of("enableUI", false)); cdpDriver.executeCdpCommand("WebAuthn.enable", Map.of("enableUI", false));
// this.driver.addVirtualAuthenticator(createVirtualAuthenticatorOptions()); // this.driver.addVirtualAuthenticator(createVirtualAuthenticatorOptions());
//@formatter:off //@formatter:off
Map<String, Object> cmdResponse = cdpDriver.executeCdpCommand("WebAuthn.addVirtualAuthenticator", cdpDriver.executeCdpCommand("WebAuthn.addVirtualAuthenticator",
Map.of( Map.of(
"options", "options",
Map.of( Map.of(
@@ -305,38 +248,21 @@ class WebAuthnWebDriverTests {
) )
)); ));
//@formatter:on //@formatter:on
this.authenticatorId = cmdResponse.get("authenticatorId").toString();
}
private void removeAuthenticator() {
HasCdp cdpDriver = (HasCdp) this.driver;
cdpDriver.executeCdpCommand("WebAuthn.removeVirtualAuthenticator",
Map.of("authenticatorId", this.authenticatorId));
this.authenticatorId = null;
} }
private void login() { private void login() {
this.getAndWait("/", "/login"); this.driver.get(this.baseUrl);
this.driver.findElement(usernameField()).sendKeys(USERNAME); this.driver.findElement(usernameField()).sendKeys(USERNAME);
this.driver.findElement(passwordField()).sendKeys(PASSWORD); this.driver.findElement(passwordField()).sendKeys(PASSWORD);
this.driver.findElement(signinWithUsernamePasswordButton()).click(); this.driver.findElement(signinWithUsernamePasswordButton()).click();
// Ensure login has completed
await(() -> assertThat(this.driver.getCurrentUrl()).doesNotContain("/login"));
} }
private void logout() { private void logout() {
this.getAndWait("/logout"); this.driver.get(this.baseUrl + "/logout");
this.driver.findElement(logoutButton()).click(); this.driver.findElement(logoutButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?logout")); await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/login?logout"));
} }
private void registerAuthenticator(String passkeyName) {
this.getAndWait("/webauthn/register");
this.driver.findElement(passkeyLabel()).sendKeys(passkeyName);
this.driver.findElement(registerPasskeyButton()).click();
await(() -> assertThat(this.driver.getCurrentUrl()).endsWith("/webauthn/register?success"));
}
private AbstractStringAssert<?> assertHasAlertStartingWith(String alertType, String alertMessage) { private AbstractStringAssert<?> assertHasAlertStartingWith(String alertType, String alertMessage) {
WebElement alert = this.driver.findElement(new By.ById(alertType)); WebElement alert = this.driver.findElement(new By.ById(alertType));
assertThat(alert.isDisplayed()) assertThat(alert.isDisplayed())
@@ -363,15 +289,6 @@ class WebAuthnWebDriverTests {
}); });
} }
private void getAndWait(String endpoint) {
this.getAndWait(endpoint, endpoint);
}
private void getAndWait(String endpoint, String redirectUrl) {
this.driver.get(this.baseUrl + endpoint);
this.await(() -> assertThat(this.driver.getCurrentUrl()).endsWith(redirectUrl));
}
private static By.ById passkeyLabel() { private static By.ById passkeyLabel() {
return new By.ById("label"); return new By.ById("label");
} }
@@ -408,10 +325,6 @@ class WebAuthnWebDriverTests {
return new By.ByCssSelector("button"); return new By.ByCssSelector("button");
} }
private static By.ByCssSelector deletePasskeyButton() {
return new By.ByCssSelector("table > tbody > tr > button");
}
/** /**
* The configuration for WebAuthN tests. It accesses the Server's current port, so we * The configuration for WebAuthN tests. It accesses the Server's current port, so we
* can configurer WebAuthnConfigurer#allowedOrigin * can configurer WebAuthnConfigurer#allowedOrigin
@@ -1,143 +0,0 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.ldap;
import javax.naming.Name;
import org.junit.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.support.BaseLdapPathAware;
import org.springframework.ldap.core.support.BaseLdapPathBeanPostProcessor;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.ldap.DefaultSpringSecurityContextSource;
import org.springframework.security.ldap.server.UnboundIdContainer;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringTestContextExtension.class)
public class Ldap247ITests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private LdapGroupDao ldapGroupDao;
@Test
public void verifyThatBasePathIsProperlyPopulated() {
this.spring.register(FromContextSourceConfig.class).autowire();
assertThat(this.ldapGroupDao).isNotNull();
assertThat(this.ldapGroupDao.getBasePath()).isNotNull();
}
@Configuration
@EnableMethodSecurity
@Import(BaseLdapServerConfig.class)
static class FromContextSourceConfig {
@Bean
AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapBindAuthenticationManagerFactory factory = new LdapBindAuthenticationManagerFactory(contextSource);
factory.setUserDnPatterns("uid={0},ou=people");
return factory.createAuthenticationManager();
}
@Bean
static MethodSecurityExpressionHandler securityExpressionHandler(LdapGroupDao ldap) {
return new MethodSecurityExpressionHandler(ldap);
}
@Bean
static LdapGroupDao ldapGroupDao() {
return new LdapGroupDao();
}
@Bean
static BaseLdapPathBeanPostProcessor baseLdapPathBeanPostProcessor() {
return new BaseLdapPathBeanPostProcessor();
}
}
@Configuration
@EnableWebSecurity
static class BaseLdapServerConfig implements DisposableBean {
private UnboundIdContainer container;
@Bean
UnboundIdContainer ldapServer() {
this.container = new UnboundIdContainer("dc=springframework,dc=org", "classpath:/test-server.ldif");
this.container.setPort(0);
return this.container;
}
@Bean
BaseLdapPathContextSource contextSource(UnboundIdContainer container) {
int port = container.getPort();
return new DefaultSpringSecurityContextSource("ldap://localhost:" + port + "/dc=springframework,dc=org");
}
@Override
public void destroy() {
this.container.stop();
}
}
static class MethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
private final LdapGroupDao groupDao;
MethodSecurityExpressionHandler(LdapGroupDao groupDao) {
this.groupDao = groupDao;
}
}
static class LdapGroupDao implements BaseLdapPathAware {
private Name basePath;
LdapGroupDao() {
super();
}
@Override
public void setBaseLdapPath(DistinguishedName baseLdapPath) {
this.basePath = baseLdapPath;
}
Name getBasePath() {
return this.basePath;
}
}
}
@@ -20,7 +20,6 @@ import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import org.jspecify.annotations.NullMarked;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -99,14 +98,12 @@ public class LdapBindAuthenticationManagerFactoryITests {
public void authenticationManagerFactoryWhenCustomUserDetailsContextMapperThenUsed() throws Exception { public void authenticationManagerFactoryWhenCustomUserDetailsContextMapperThenUsed() throws Exception {
CustomUserDetailsContextMapperConfig.CONTEXT_MAPPER = new UserDetailsContextMapper() { CustomUserDetailsContextMapperConfig.CONTEXT_MAPPER = new UserDetailsContextMapper() {
@Override @Override
@NullMarked
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, public UserDetails mapUserFromContext(DirContextOperations ctx, String username,
Collection<? extends GrantedAuthority> authorities) { Collection<? extends GrantedAuthority> authorities) {
return User.withUsername("other").password("password").roles("USER").build(); return User.withUsername("other").password("password").roles("USER").build();
} }
@Override @Override
@NullMarked
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) { public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
} }
}; };
@@ -94,7 +94,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
public BeanDefinition parse(Element element, ParserContext pc) { public BeanDefinition parse(Element element, ParserContext pc) {
if (!namespaceMatchesVersion(element)) { if (!namespaceMatchesVersion(element)) {
pc.getReaderContext() pc.getReaderContext()
.fatal("You cannot use any XSD older than spring-security-7.1.xsd. Either change to spring-security.xsd or spring-security-7.1.xsd", .fatal("You cannot use any XSD older than spring-security-7.0.xsd. Either change to spring-security.xsd or spring-security-7.0.xsd",
element); element);
} }
String name = pc.getDelegate().getLocalName(element); String name = pc.getDelegate().getLocalName(element);
@@ -219,7 +219,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
private boolean matchesVersionInternal(Element element) { private boolean matchesVersionInternal(Element element) {
String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation"); String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation");
return schemaLocation.matches("(?m).*spring-security-7\\.1.*.xsd.*") return schemaLocation.matches("(?m).*spring-security-7\\.0.*.xsd.*")
|| schemaLocation.matches("(?m).*spring-security.xsd.*") || schemaLocation.matches("(?m).*spring-security.xsd.*")
|| !schemaLocation.matches("(?m).*spring-security.*"); || !schemaLocation.matches("(?m).*spring-security.*");
} }
@@ -177,7 +177,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
} }
/** /**
* Gets a shared Object. Note that object hierarchies are not considered. * Gets a shared Object. Note that object heirarchies are not considered.
* @param sharedType the type of the shared Object * @param sharedType the type of the shared Object
* @return the shared Object or null if it is not found * @return the shared Object or null if it is not found
*/ */
@@ -360,7 +360,7 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
/** /**
* Subclasses must implement this method to build the object that is being returned. * Subclasses must implement this method to build the object that is being returned.
* @return the Object to be built or null if the implementation allows it * @return the Object to be buit or null if the implementation allows it
*/ */
protected abstract O performBuild(); protected abstract O performBuild();
@@ -414,13 +414,13 @@ public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBui
private enum BuildState { private enum BuildState {
/** /**
* This is the state before the {@link SecurityBuilder#build()} is invoked * This is the state before the {@link Builder#build()} is invoked
*/ */
UNBUILT(0), UNBUILT(0),
/** /**
* The state from when {@link SecurityBuilder#build()} is first invoked until all * The state from when {@link Builder#build()} is first invoked until all the
* the {@link SecurityConfigurer#init(SecurityBuilder)} methods have been invoked. * {@link SecurityConfigurer#init(SecurityBuilder)} methods have been invoked.
*/ */
INITIALIZING(1), INITIALIZING(1),
@@ -36,7 +36,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.log.LogMessage; import org.springframework.core.log.LogMessage;
import org.springframework.lang.Contract;
import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
@@ -303,7 +302,6 @@ public class AuthenticationConfiguration {
} }
@Override @Override
@Contract("!null -> !null; null -> null")
public String encode(CharSequence rawPassword) { public String encode(CharSequence rawPassword) {
return getPasswordEncoder().encode(rawPassword); return getPasswordEncoder().encode(rawPassword);
} }
@@ -42,8 +42,7 @@ final class MethodSecuritySelector implements ImportSelector {
.isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null); .isPresent("org.springframework.security.data.aot.hint.AuthorizeReturnObjectDataHintsRegistrar", null);
private static final boolean isWebPresent = ClassUtils private static final boolean isWebPresent = ClassUtils
.isPresent("org.springframework.web.servlet.DispatcherServlet", null) .isPresent("org.springframework.web.servlet.DispatcherServlet", null);
&& ClassUtils.isPresent("org.springframework.security.web.util.ThrowableAnalyzer", null);
private static final boolean isObservabilityPresent = ClassUtils private static final boolean isObservabilityPresent = ClassUtils
.isPresent("io.micrometer.observation.ObservationRegistry", null); .isPresent("io.micrometer.observation.ObservationRegistry", null);
@@ -82,7 +82,7 @@ public interface HttpSecurityBuilder<H extends HttpSecurityBuilder<H>>
<C> void setSharedObject(Class<C> sharedType, C object); <C> void setSharedObject(Class<C> sharedType, C object);
/** /**
* Gets a shared Object. Note that object hierarchies are not considered. * Gets a shared Object. Note that object heirarchies are not considered.
* @param sharedType the type of the shared Object * @param sharedType the type of the shared Object
* @return the shared Object or null if it is not found * @return the shared Object or null if it is not found
*/ */
@@ -133,7 +133,7 @@ final class FilterOrderRegistration {
/** /**
* Register a {@link Filter} with its specific position. If the {@link Filter} was * Register a {@link Filter} with its specific position. If the {@link Filter} was
* already registered before, the position previously defined is not going to be * already registered before, the position previously defined is not going to be
* overridden * overriden
* @param filter the {@link Filter} to register * @param filter the {@link Filter} to register
* @param position the position to associate with the {@link Filter} * @param position the position to associate with the {@link Filter}
*/ */
@@ -1881,7 +1881,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
* *
* <p> * <p>
* Invoking {@link #securityMatchers(Customizer)} will not override previous * Invoking {@link #securityMatchers(Customizer)} will not override previous
* invocations of {@link #securityMatchers(Customizer)} * invocations of {@link #securityMatchers()}}, {@link #securityMatchers(Customizer)}
* {@link #securityMatcher(String...)} and {@link #securityMatcher(RequestMatcher)} * {@link #securityMatcher(String...)} and {@link #securityMatcher(RequestMatcher)}
* </p> * </p>
* *
@@ -2004,7 +2004,8 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
* <p> * <p>
* Invoking {@link #securityMatcher(RequestMatcher)} will override previous * Invoking {@link #securityMatcher(RequestMatcher)} will override previous
* invocations of {@link #securityMatcher(RequestMatcher)}, * invocations of {@link #securityMatcher(RequestMatcher)},
* {@link #securityMatcher(String...)} and {@link #securityMatchers(Customizer)} * {@link #securityMatcher(String...)}, {@link #securityMatchers(Customizer)} and
* {@link #securityMatchers()}
* </p> * </p>
* @param requestMatcher the {@link RequestMatcher} to use, for example, * @param requestMatcher the {@link RequestMatcher} to use, for example,
* {@code PathPatternRequestMatcher.pathPattern(HttpMethod.GET, "/admin/**")} * {@code PathPatternRequestMatcher.pathPattern(HttpMethod.GET, "/admin/**")}
@@ -2023,8 +2024,9 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
* *
* <p> * <p>
* Invoking {@link #securityMatcher(String...)} will override previous invocations of * Invoking {@link #securityMatcher(String...)} will override previous invocations of
* {@link #securityMatcher(String...)}, {@link #securityMatcher(RequestMatcher)} and * {@link #securityMatcher(String...)} (String)}},
* {@link #securityMatchers(Customizer)}. * {@link #securityMatcher(RequestMatcher)} ()}, {@link #securityMatchers(Customizer)}
* (String)} and {@link #securityMatchers()} (String)}.
* </p> * </p>
* @param patterns the pattern to match on (i.e. "/admin/**") * @param patterns the pattern to match on (i.e. "/admin/**")
* @return the {@link HttpSecurity} for further customizations * @return the {@link HttpSecurity} for further customizations
@@ -2050,6 +2052,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
* http * http
* // ... * // ...
* .webAuthn((webAuthn) -&gt; webAuthn * .webAuthn((webAuthn) -&gt; webAuthn
* .rpName("Spring Security Relying Party")
* .rpId("example.com") * .rpId("example.com")
* .allowedOrigins("https://example.com") * .allowedOrigins("https://example.com")
* ); * );
@@ -226,7 +226,7 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
* *
* <p> * <p>
* Typically this method is invoked automatically within the framework from * Typically this method is invoked automatically within the framework from
* {@link WebSecurityConfiguration#springSecurityFilterChain(ObjectProvider)} * {@link WebSecurityConfiguration#springSecurityFilterChain()}
* </p> * </p>
* @param securityFilterChainBuilder the builder to use to create the * @param securityFilterChainBuilder the builder to use to create the
* {@link SecurityFilterChain} instances * {@link SecurityFilterChain} instances
@@ -30,7 +30,6 @@ import org.springframework.context.annotation.Scope;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.lang.Contract;
import org.springframework.security.authentication.AuthenticationEventPublisher; import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
@@ -294,7 +293,6 @@ class HttpSecurityConfiguration {
} }
@Override @Override
@Contract("!null -> !null; null -> null")
public String encode(CharSequence rawPassword) { public String encode(CharSequence rawPassword) {
return getPasswordEncoder().encode(rawPassword); return getPasswordEncoder().encode(rawPassword);
} }
@@ -39,7 +39,6 @@ import org.springframework.security.web.authentication.AnonymousAuthenticationFi
* other than applying this {@link SecurityConfigurer}. * other than applying this {@link SecurityConfigurer}.
* *
* @author Rob Winch * @author Rob Winch
* @author DingHao
* @since 3.2 * @since 3.2
*/ */
public final class AnonymousConfigurer<H extends HttpSecurityBuilder<H>> public final class AnonymousConfigurer<H extends HttpSecurityBuilder<H>>
@@ -159,7 +158,7 @@ public final class AnonymousConfigurer<H extends HttpSecurityBuilder<H>>
} }
this.authenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy()); this.authenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
this.authenticationFilter.afterPropertiesSet(); this.authenticationFilter.afterPropertiesSet();
http.addFilter(postProcess(this.authenticationFilter)); http.addFilter(this.authenticationFilter);
} }
private String getKey() { private String getKey() {
@@ -305,7 +305,7 @@ public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>>
} }
/** /**
* Gets the logoutSuccessUrl or null if a * Gets the logoutSuccesUrl or null if a
* {@link #logoutSuccessHandler(LogoutSuccessHandler)} was configured. * {@link #logoutSuccessHandler(LogoutSuccessHandler)} was configured.
* @return the logoutSuccessUrl * @return the logoutSuccessUrl
*/ */
@@ -146,7 +146,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
/** /**
* This should not use RequestAttributeSecurityContextRepository since that is * This should not use RequestAttributeSecurityContextRepository since that is
* stateless and session management is about state management. * stateless and sesison management is about state management.
*/ */
private SecurityContextRepository sessionManagementSecurityContextRepository = new HttpSessionSecurityContextRepository(); private SecurityContextRepository sessionManagementSecurityContextRepository = new HttpSessionSecurityContextRepository();
@@ -177,7 +177,6 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter(); WebAuthnAuthenticationFilter webAuthnAuthnFilter = new WebAuthnAuthenticationFilter();
webAuthnAuthnFilter.setAuthenticationManager( webAuthnAuthnFilter.setAuthenticationManager(
new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService))); new ProviderManager(new WebAuthnAuthenticationProvider(rpOperations, userDetailsService)));
webAuthnAuthnFilter = postProcess(webAuthnAuthnFilter);
WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials, WebAuthnRegistrationFilter webAuthnRegistrationFilter = new WebAuthnRegistrationFilter(userCredentials,
rpOperations); rpOperations);
PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter( PublicKeyCredentialCreationOptionsFilter creationOptionsFilter = new PublicKeyCredentialCreationOptionsFilter(
@@ -257,10 +256,9 @@ public class WebAuthnConfigurer<H extends HttpSecurityBuilder<H>>
PublicKeyCredentialUserEntityRepository userEntities, UserCredentialRepository userCredentials) { PublicKeyCredentialUserEntityRepository userEntities, UserCredentialRepository userCredentials) {
Optional<WebAuthnRelyingPartyOperations> webauthnOperationsBean = getBeanOrNull( Optional<WebAuthnRelyingPartyOperations> webauthnOperationsBean = getBeanOrNull(
WebAuthnRelyingPartyOperations.class); WebAuthnRelyingPartyOperations.class);
String rpName = (this.rpName != null) ? this.rpName : this.rpId; return webauthnOperationsBean.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities,
return webauthnOperationsBean userCredentials, PublicKeyCredentialRpEntity.builder().id(this.rpId).name(this.rpName).build(),
.orElseGet(() -> new Webauthn4JRelyingPartyOperations(userEntities, userCredentials, this.allowedOrigins));
PublicKeyCredentialRpEntity.builder().id(this.rpId).name(rpName).build(), this.allowedOrigins));
} }
} }
@@ -47,8 +47,8 @@ import org.springframework.util.Assert;
* The following configuration options are available: * The following configuration options are available:
* *
* <ul> * <ul>
* <li>{@link #authorizationCodeGrant(Customizer)} - support for the OAuth 2.0 * <li>{@link #authorizationCodeGrant()} - support for the OAuth 2.0 Authorization Code
* Authorization Code Grant</li> * Grant</li>
* </ul> * </ul>
* *
* <p> * <p>
@@ -59,8 +59,7 @@ import org.springframework.util.Assert;
* *
* <h2>Security Filters</h2> * <h2>Security Filters</h2>
* *
* The following {@code Filter}'s are populated for * The following {@code Filter}'s are populated for {@link #authorizationCodeGrant()}:
* {@link #authorizationCodeGrant(Customizer)}:
* *
* <ul> * <ul>
* <li>{@link OAuth2AuthorizationRequestRedirectFilter}</li> * <li>{@link OAuth2AuthorizationRequestRedirectFilter}</li>
@@ -16,12 +16,10 @@
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
import jakarta.servlet.Filter;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
@@ -38,12 +36,10 @@ import org.springframework.security.oauth2.server.authorization.authentication.O
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationValidator; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationValidator;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken; import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
@@ -54,7 +50,6 @@ import org.springframework.security.web.servlet.util.matcher.PathPatternRequestM
import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@@ -88,8 +83,6 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidator; private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidator;
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationCodeRequestAuthenticationValidatorComposite;
private SessionAuthenticationStrategy sessionAuthenticationStrategy; private SessionAuthenticationStrategy sessionAuthenticationStrategy;
/** /**
@@ -255,16 +248,8 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
authenticationProviders.addAll(0, this.authenticationProviders); authenticationProviders.addAll(0, this.authenticationProviders);
} }
this.authenticationProvidersConsumer.accept(authenticationProviders); this.authenticationProvidersConsumer.accept(authenticationProviders);
authenticationProviders.forEach((authenticationProvider) -> { authenticationProviders.forEach(
httpSecurity.authenticationProvider(postProcess(authenticationProvider)); (authenticationProvider) -> httpSecurity.authenticationProvider(postProcess(authenticationProvider)));
if (authenticationProvider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider) {
Method method = ReflectionUtils.findMethod(OAuth2AuthorizationCodeRequestAuthenticationProvider.class,
"getAuthenticationValidatorComposite");
ReflectionUtils.makeAccessible(method);
this.authorizationCodeRequestAuthenticationValidatorComposite = (Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext>) ReflectionUtils
.invokeMethod(method, authenticationProvider);
}
});
} }
@Override @Override
@@ -297,18 +282,7 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
if (this.sessionAuthenticationStrategy != null) { if (this.sessionAuthenticationStrategy != null) {
authorizationEndpointFilter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy); authorizationEndpointFilter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy);
} }
httpSecurity.addFilterAfter(postProcess(authorizationEndpointFilter), AuthorizationFilter.class); httpSecurity.addFilterBefore(postProcess(authorizationEndpointFilter),
// Create and add
// OAuth2AuthorizationEndpointFilter.OAuth2AuthorizationCodeRequestValidatingFilter
Method method = ReflectionUtils.findMethod(OAuth2AuthorizationEndpointFilter.class,
"createAuthorizationCodeRequestValidatingFilter", RegisteredClientRepository.class, Consumer.class);
ReflectionUtils.makeAccessible(method);
RegisteredClientRepository registeredClientRepository = OAuth2ConfigurerUtils
.getRegisteredClientRepository(httpSecurity);
Filter authorizationCodeRequestValidatingFilter = (Filter) ReflectionUtils.invokeMethod(method,
authorizationEndpointFilter, registeredClientRepository,
this.authorizationCodeRequestAuthenticationValidatorComposite);
httpSecurity.addFilterBefore(postProcess(authorizationCodeRequestValidatingFilter),
AbstractPreAuthenticatedProcessingFilter.class); AbstractPreAuthenticatedProcessingFilter.class);
} }
@@ -16,9 +16,14 @@
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization; package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
import java.util.Map;
import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext; import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
@@ -40,6 +45,7 @@ import org.springframework.security.oauth2.server.authorization.token.OAuth2Toke
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/** /**
* Utility methods for the OAuth 2.0 Configurers. * Utility methods for the OAuth 2.0 Configurers.
@@ -201,16 +207,41 @@ final class OAuth2ConfigurerUtils {
} }
static <T> T getBean(HttpSecurity httpSecurity, Class<T> type) { static <T> T getBean(HttpSecurity httpSecurity, Class<T> type) {
return httpSecurity.getSharedObject(ApplicationContext.class).getBeanProvider(type).getObject(); return httpSecurity.getSharedObject(ApplicationContext.class).getBean(type);
}
@SuppressWarnings("unchecked")
static <T> T getBean(HttpSecurity httpSecurity, ResolvableType type) {
ApplicationContext context = httpSecurity.getSharedObject(ApplicationContext.class);
String[] names = context.getBeanNamesForType(type);
if (names.length == 1) {
return (T) context.getBean(names[0]);
}
if (names.length > 1) {
throw new NoUniqueBeanDefinitionException(type, names);
}
throw new NoSuchBeanDefinitionException(type);
} }
static <T> T getOptionalBean(HttpSecurity httpSecurity, Class<T> type) { static <T> T getOptionalBean(HttpSecurity httpSecurity, Class<T> type) {
return httpSecurity.getSharedObject(ApplicationContext.class).getBeanProvider(type).getIfUnique(); Map<String, T> beansMap = BeanFactoryUtils
.beansOfTypeIncludingAncestors(httpSecurity.getSharedObject(ApplicationContext.class), type);
if (beansMap.size() > 1) {
throw new NoUniqueBeanDefinitionException(type, beansMap.size(),
"Expected single matching bean of type '" + type.getName() + "' but found " + beansMap.size() + ": "
+ StringUtils.collectionToCommaDelimitedString(beansMap.keySet()));
}
return (!beansMap.isEmpty() ? beansMap.values().iterator().next() : null);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
static <T> T getOptionalBean(HttpSecurity httpSecurity, ResolvableType type) { static <T> T getOptionalBean(HttpSecurity httpSecurity, ResolvableType type) {
return (T) httpSecurity.getSharedObject(ApplicationContext.class).getBeanProvider(type).getIfUnique(); ApplicationContext context = httpSecurity.getSharedObject(ApplicationContext.class);
String[] names = context.getBeanNamesForType(type);
if (names.length > 1) {
throw new NoUniqueBeanDefinitionException(type, names);
}
return (names.length == 1) ? (T) context.getBean(names[0]) : null;
} }
} }
@@ -40,11 +40,11 @@ import org.springframework.security.oauth2.server.authorization.settings.Authori
import org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceVerificationEndpointFilter; import org.springframework.security.oauth2.server.authorization.web.OAuth2DeviceVerificationEndpointFilter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceAuthorizationConsentAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceAuthorizationConsentAuthenticationConverter;
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceVerificationAuthenticationConverter; import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceVerificationAuthenticationConverter;
import org.springframework.security.web.access.intercept.AuthorizationFilter;
import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.DelegatingAuthenticationConverter; import org.springframework.security.web.authentication.DelegatingAuthenticationConverter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -279,7 +279,8 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
if (StringUtils.hasText(this.consentPage)) { if (StringUtils.hasText(this.consentPage)) {
deviceVerificationEndpointFilter.setConsentPage(this.consentPage); deviceVerificationEndpointFilter.setConsentPage(this.consentPage);
} }
builder.addFilterAfter(postProcess(deviceVerificationEndpointFilter), AuthorizationFilter.class); builder.addFilterBefore(postProcess(deviceVerificationEndpointFilter),
AbstractPreAuthenticatedProcessingFilter.class);
} }
@Override @Override
@@ -521,10 +521,8 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
public OpaqueTokenConfigurer introspectionUri(String introspectionUri) { public OpaqueTokenConfigurer introspectionUri(String introspectionUri) {
Assert.notNull(introspectionUri, "introspectionUri cannot be null"); Assert.notNull(introspectionUri, "introspectionUri cannot be null");
this.introspectionUri = introspectionUri; this.introspectionUri = introspectionUri;
this.introspector = () -> SpringOpaqueTokenIntrospector.withIntrospectionUri(this.introspectionUri) this.introspector = () -> new SpringOpaqueTokenIntrospector(this.introspectionUri, this.clientId,
.clientId(this.clientId) this.clientSecret);
.clientSecret(this.clientSecret)
.build();
return this; return this;
} }
@@ -533,10 +531,8 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
Assert.notNull(clientSecret, "clientSecret cannot be null"); Assert.notNull(clientSecret, "clientSecret cannot be null");
this.clientId = clientId; this.clientId = clientId;
this.clientSecret = clientSecret; this.clientSecret = clientSecret;
this.introspector = () -> SpringOpaqueTokenIntrospector.withIntrospectionUri(this.introspectionUri) this.introspector = () -> new SpringOpaqueTokenIntrospector(this.introspectionUri, this.clientId,
.clientId(this.clientId) this.clientSecret);
.clientSecret(this.clientSecret)
.build();
return this; return this;
} }
@@ -21,7 +21,6 @@ import java.util.function.Function;
import org.opensaml.core.Version; import org.opensaml.core.Version;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder; import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
@@ -43,8 +42,7 @@ import org.springframework.util.Assert;
* *
* <p> * <p>
* Defaults are provided for all configuration options with the only required * Defaults are provided for all configuration options with the only required
* configuration being a * configuration being a {@link Saml2LoginConfigurer#relyingPartyRegistrationRepository}.
* {@link Saml2LoginConfigurer#relyingPartyRegistrationRepository(HttpSecurityBuilder)}.
* Alternatively, a {@link RelyingPartyRegistrationRepository} {@code @Bean} may be * Alternatively, a {@link RelyingPartyRegistrationRepository} {@code @Bean} may be
* registered instead. * registered instead.
* *
@@ -69,7 +67,7 @@ import org.springframework.util.Assert;
* </ul> * </ul>
* *
* @since 6.1 * @since 6.1
* @see HttpSecurity#saml2Metadata(Customizer) * @see HttpSecurity#saml2Metadata()
* @see Saml2MetadataFilter * @see Saml2MetadataFilter
* @see RelyingPartyRegistrationRepository * @see RelyingPartyRegistrationRepository
*/ */
@@ -97,7 +95,7 @@ public class Saml2MetadataConfigurer<H extends HttpSecurityBuilder<H>>
* If there is no {@code registrationId} and your * If there is no {@code registrationId} and your
* {@link RelyingPartyRegistrationRepository} is {code Iterable}, the metadata * {@link RelyingPartyRegistrationRepository} is {code Iterable}, the metadata
* endpoint will try and show all relying parties' metadata in a single * endpoint will try and show all relying parties' metadata in a single
* {@code <md:EntitiesDescriptor} element. * {@code <md:EntitiesDecriptor} element.
* *
* <p> * <p>
* If you need a more sophisticated lookup strategy than these, use * If you need a more sophisticated lookup strategy than these, use
@@ -167,7 +167,7 @@ class ServerHttpSecurityConfiguration {
} }
/** /**
* Applies all {@code Customizer<ServerHttpSecurity>} Beans to * Applies all {@code Custmizer<ServerHttpSecurity>} Beans to
* {@link ServerHttpSecurity}. * {@link ServerHttpSecurity}.
* @param context the {@link ApplicationContext} * @param context the {@link ApplicationContext}
* @param http the {@link ServerHttpSecurity} * @param http the {@link ServerHttpSecurity}
@@ -255,9 +255,7 @@ class ServerHttpSecurityConfiguration {
if (this.passwordEncoder != null) { if (this.passwordEncoder != null) {
manager.setPasswordEncoder(this.passwordEncoder); manager.setPasswordEncoder(this.passwordEncoder);
} }
if (this.userDetailsPasswordService != null) { manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
manager.setUserDetailsPasswordService(this.userDetailsPasswordService);
}
manager.setCompromisedPasswordChecker(this.compromisedPasswordChecker); manager.setCompromisedPasswordChecker(this.compromisedPasswordChecker);
return this.postProcessor.postProcess(manager); return this.postProcessor.postProcess(manager);
} }
@@ -538,7 +538,7 @@ final class AuthenticationConfigBuilder {
} }
injectAuthenticationDetailsSource(x509Elt, filterBuilder); injectAuthenticationDetailsSource(x509Elt, filterBuilder);
filter = (RootBeanDefinition) filterBuilder.getBeanDefinition(); filter = (RootBeanDefinition) filterBuilder.getBeanDefinition();
createPreauthEntryPoint(x509Elt); createPrauthEntryPoint(x509Elt);
createX509Provider(); createX509Provider();
} }
this.x509Filter = filter; this.x509Filter = filter;
@@ -562,7 +562,7 @@ final class AuthenticationConfigBuilder {
this.x509ProviderRef = new RuntimeBeanReference(this.pc.getReaderContext().registerWithGeneratedName(provider)); this.x509ProviderRef = new RuntimeBeanReference(this.pc.getReaderContext().registerWithGeneratedName(provider));
} }
private void createPreauthEntryPoint(Element source) { private void createPrauthEntryPoint(Element source) {
if (this.preAuthEntryPoint == null) { if (this.preAuthEntryPoint == null) {
this.preAuthEntryPoint = new RootBeanDefinition(Http403ForbiddenEntryPoint.class); this.preAuthEntryPoint = new RootBeanDefinition(Http403ForbiddenEntryPoint.class);
this.preAuthEntryPoint.setSource(this.pc.extractSource(source)); this.preAuthEntryPoint.setSource(this.pc.extractSource(source));
@@ -595,7 +595,7 @@ final class AuthenticationConfigBuilder {
adsBldr.addPropertyValue("mappableRolesRetriever", mappableRolesRetriever); adsBldr.addPropertyValue("mappableRolesRetriever", mappableRolesRetriever);
filterBuilder.addPropertyValue("authenticationDetailsSource", adsBldr.getBeanDefinition()); filterBuilder.addPropertyValue("authenticationDetailsSource", adsBldr.getBeanDefinition());
filter = (RootBeanDefinition) filterBuilder.getBeanDefinition(); filter = (RootBeanDefinition) filterBuilder.getBeanDefinition();
createPreauthEntryPoint(jeeElt); createPrauthEntryPoint(jeeElt);
createJeeProvider(); createJeeProvider();
} }
this.jeeFilter = filter; this.jeeFilter = filter;

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