diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index f4fe653cebd..00000000000 --- a/.gitattributes +++ /dev/null @@ -1,18 +0,0 @@ -# Normalize line endings to auto. -* text auto - -# Ensure that line endings for DOS batch files are not modified. -*.bat -text - -# Ensure the following are treated as binary. -*.cer binary -*.graffle binary -*.jar binary -*.jpeg binary -*.jpg binary -*.keystore binary -*.odg binary -*.otg binary -*.png binary -*.hsx binary -*.serialized binary diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 0de3f4ab6cf..00000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,40 +0,0 @@ - - -### Summary - - - -### Actual Behavior - - - -### Expected Behavior - - - -### Configuration - - - -### Version - - - -### Sample - - diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md deleted file mode 100644 index cf2d41795ad..00000000000 --- a/.github/ISSUE_TEMPLATE/bug.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: Bug -about: Create a bug report to help us improve -title: '' -labels: 'status: waiting-for-triage, type: bug' -assignees: '' - ---- - - - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior. - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Sample** - -A link to a GitHub repository with a [minimal, reproducible sample](https://stackoverflow.com/help/minimal-reproducible-example). - -Reports that include a sample will take priority over reports that do not. -At times, we may require a sample, so it is good to try and include a sample up front. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 4ba41d1b38b..00000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Community Support - url: https://stackoverflow.com/questions/tagged/spring-security - about: Please ask and answer questions on StackOverflow with the tag `spring-security`. diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md deleted file mode 100644 index 14d9fb49273..00000000000 --- a/.github/ISSUE_TEMPLATE/enhancement.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -name: Enhancement -about: Suggest an enhancement for this project -title: '' -labels: 'status: waiting-for-triage, type: enhancement' -assignees: '' - ---- - -**Expected Behavior** - - - -**Current Behavior** - - - -**Context** - - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 060d79039e4..00000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/core/src/main/resources/org/springframework/security/messages_nl.properties b/.github/actions/.gitkeep similarity index 100% rename from core/src/main/resources/org/springframework/security/messages_nl.properties rename to .github/actions/.gitkeep diff --git a/.github/dco.yml b/.github/dco.yml deleted file mode 100644 index 0c4b142e9a7..00000000000 --- a/.github/dco.yml +++ /dev/null @@ -1,2 +0,0 @@ -require: - members: false diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 96e99826821..00000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,121 +0,0 @@ -version: 2 -registries: - spring-milestones: - type: maven-repository - url: https://repo.spring.io/milestone - shibboleth: - type: maven-repository - url: https://build.shibboleth.net/maven/releases -updates: - - package-ecosystem: gradle - target-branch: 6.5.x - directory: / - schedule: - interval: daily - time: '03:00' - timezone: Etc/UTC - labels: - - 'type: dependency-upgrade' - registries: - - spring-milestones - - 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: '*' - update-types: - - version-update:semver-major - - version-update:semver-minor - - package-ecosystem: gradle - target-branch: 6.4.x - directory: / - schedule: - interval: daily - time: '03:00' - timezone: Etc/UTC - labels: - - 'type: dependency-upgrade' - registries: - - spring-milestones - - 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: '*' - update-types: - - version-update:semver-major - - version-update:semver-minor - - - package-ecosystem: gradle - target-branch: main - directory: / - schedule: - interval: daily - time: '03:00' - timezone: Etc/UTC - labels: - - 'type: dependency-upgrade' - registries: - - spring-milestones - - 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 - - version-update:semver-minor - - - package-ecosystem: npm - target-branch: docs-build - directory: / - schedule: - interval: weekly - labels: - - 'type: task' - - 'in: build' - - - package-ecosystem: npm - target-branch: main - directory: /docs - schedule: - interval: weekly - labels: - - 'type: task' - - 'in: build' - - package-ecosystem: npm - target-branch: 6.3.x - directory: /docs - schedule: - interval: weekly - labels: - - 'type: task' - - 'in: build' diff --git a/.github/workflows/check-snapshots.yml b/.github/workflows/check-snapshots.yml deleted file mode 100644 index f482c0459fe..00000000000 --- a/.github/workflows/check-snapshots.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: CI - -on: - schedule: - - cron: '0 10 * * *' # Once per day at 10am UTC - workflow_dispatch: # Manual trigger - -env: - DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} - -permissions: - contents: read - -jobs: - snapshot-test: - name: Test Against Snapshots - uses: spring-io/spring-security-release-tools/.github/workflows/test.yml@v1 - strategy: - matrix: - include: - - java-version: 21-ea - toolchain: 21 - - java-version: 17 - toolchain: 17 - with: - 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 - secrets: inherit - send-notification: - name: Send Notification - needs: [ snapshot-test ] - 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 }} diff --git a/.github/workflows/clean_build_artifacts.yml b/.github/workflows/clean_build_artifacts.yml deleted file mode 100644 index c116fac71d4..00000000000 --- a/.github/workflows/clean_build_artifacts.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Clean build artifacts -on: - schedule: - - cron: '0 10 * * *' # Once per day at 10am UTC - -permissions: - contents: read - -jobs: - main: - runs-on: ubuntu-latest - if: ${{ github.repository == 'spring-projects/spring-security' }} - permissions: - contents: none - steps: - - name: Delete artifacts in cron job - env: - GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} - run: | - echo "Running clean build artifacts logic" - output=$(curl -X GET -H "Authorization: token $GH_ACTIONS_REPO_TOKEN" https://api.github.com/repos/spring-projects/spring-security/actions/artifacts | grep '"id"' | cut -d : -f2 | sed 's/,*$//g') - echo Output is $output - for id in $output; do curl -X DELETE -H "Authorization: token $GH_ACTIONS_REPO_TOKEN" https://api.github.com/repos/spring-projects/spring-security/actions/artifacts/$id; done; diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index dabe0665f03..cdbafcc88a2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,17 +1,80 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# name: "CodeQL Advanced" on: - push: - pull_request: + push: # run if we update the workflow workflow_dispatch: schedule: - # https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#schedule - - cron: '0 5 * * *' -permissions: read-all + - cron: '39 13 * * 4' + jobs: - codeql-analysis-call: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ubuntu-latest permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories actions: read contents: read - security-events: write - uses: spring-io/github-actions/.github/workflows/codeql-analysis.yml@1 + + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v5 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + queries: security-extended,security-and-quality + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/continuous-integration-workflow.yml b/.github/workflows/continuous-integration-workflow.yml deleted file mode 100644 index 12e9eec7549..00000000000 --- a/.github/workflows/continuous-integration-workflow.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: CI - -on: - push: - branches-ignore: - - "dependabot/**" - schedule: - - cron: '0 10 * * *' # Once per day at 10am UTC - workflow_dispatch: # Manual trigger - -env: - DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} - -permissions: - contents: read - -jobs: - build: - name: Build - uses: spring-io/spring-security-release-tools/.github/workflows/build.yml@v1 - strategy: - matrix: - os: [ ubuntu-latest, windows-latest ] - jdk: [ 17 ] - with: - runs-on: ${{ matrix.os }} - java-version: ${{ matrix.jdk }} - distribution: temurin - secrets: inherit - deploy-artifacts: - name: Deploy Artifacts - needs: [ build] - uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@v1 - with: - should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }} - default-publish-milestones-central: true - secrets: inherit - deploy-schema: - name: Deploy Schema - needs: [ build ] - uses: spring-io/spring-security-release-tools/.github/workflows/deploy-schema.yml@v1 - with: - should-deploy-schema: ${{ needs.build.outputs.should-deploy-artifacts }} - secrets: inherit - perform-release: - name: Perform Release - needs: [ deploy-artifacts, deploy-schema ] - uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1 - with: - should-perform-release: ${{ needs.deploy-artifacts.outputs.artifacts-deployed }} - project-version: ${{ needs.deploy-artifacts.outputs.project-version }} - milestone-repo-url: https://repo1.maven.org/maven2 - release-repo-url: https://repo1.maven.org/maven2 - artifact-path: org/springframework/security/spring-security-core - slack-announcing-id: spring-security-announcing - 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 }} diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 25381d0f820..41e746344ba 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -1,33 +1,50 @@ name: Deploy Docs +run-name: ${{ format('{0} ({1})', github.workflow, github.event.inputs.build-refname || 'all') }} on: - push: - branches-ignore: - - "gh-pages" - - "dependabot/**" - tags: '**' - repository_dispatch: - types: request-build-reference # legacy - #schedule: - #- cron: '0 10 * * *' # Once per day at 10am UTC workflow_dispatch: + inputs: + build-refname: + description: Enter git refname to build (e.g., 5.7.x). + required: false + push: + branches: docs-build +env: + GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} permissions: read-all jobs: build: - runs-on: ubuntu-latest if: github.repository_owner == 'spring-projects' + runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 + with: + fetch-depth: 5 + - name: Set Up Gradle + uses: spring-io/spring-gradle-build-action@v2 + with: + java-version: '17' + distribution: temurin + - name: Set up refname build + if: github.event.inputs.build-refname + run: | + git fetch --depth 1 https://github.com/$GITHUB_REPOSITORY ${{ github.event.inputs.build-refname }} + echo BUILD_REFNAME=${{ github.event.inputs.build-refname }} >> $GITHUB_ENV + echo BUILD_VERSION=$(git cat-file --textconv FETCH_HEAD:gradle.properties | sed -n '/^version=/ { s/^version=//;p }') >> $GITHUB_ENV + - name: Run Antora + run: ./gradlew antora + - name: Copy the cache to be included in the site + run: cp -rf build/antora/inject-collector-cache-config-extension/.cache build/site/ + - name: Publish Docs + uses: spring-io/spring-doc-actions/rsync-antora-reference@v0.0.21 + with: + docs-username: ${{ secrets.DOCS_USERNAME }} + docs-host: ${{ secrets.DOCS_HOST }} + docs-ssh-key: ${{ secrets.DOCS_SSH_KEY }} + docs-ssh-host-key: ${{ secrets.DOCS_SSH_HOST_KEY }} + - name: Bust Clouflare Cache + uses: spring-io/spring-doc-actions/bust-cloudflare-antora-cache@v0.0.21 with: - ref: docs-build - fetch-depth: 1 - - name: Dispatch (partial build) - if: github.ref_type == 'branch' - env: - GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} - run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) -f build-refname=${{ github.ref_name }} - - name: Dispatch (full build) - if: github.ref_type == 'tag' - env: - GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} - run: gh workflow run deploy-docs.yml -r $(git rev-parse --abbrev-ref HEAD) + context-root: spring-security + cloudflare-zone-id: ${{ secrets.CLOUDFLARE_ZONE_ID }} + cloudflare-cache-token: ${{ secrets.CLOUDFLARE_CACHE_TOKEN }} diff --git a/.github/workflows/finalize-release.yml b/.github/workflows/finalize-release.yml deleted file mode 100644 index 0635f267483..00000000000 --- a/.github/workflows/finalize-release.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Finalize Release - -on: - workflow_dispatch: # Manual trigger - inputs: - version: - description: The Spring Security release to finalize (e.g. 7.0.0-RC2) - required: true - -env: - DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} - -permissions: - contents: read - -jobs: - perform-release: - name: Perform Release - uses: spring-io/spring-security-release-tools/.github/workflows/perform-release.yml@v1 - with: - should-perform-release: true - project-version: ${{ inputs.version }} - milestone-repo-url: https://repo1.maven.org/maven2 - release-repo-url: https://repo1.maven.org/maven2 - artifact-path: org/springframework/security/spring-security-core - slack-announcing-id: spring-security-announcing - secrets: inherit diff --git a/.github/workflows/gradle-wrapper-upgrade-execution.yml b/.github/workflows/gradle-wrapper-upgrade-execution.yml deleted file mode 100644 index 8207edddefb..00000000000 --- a/.github/workflows/gradle-wrapper-upgrade-execution.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Execute Gradle Wrapper Upgrade - -on: - schedule: - - cron: '0 2 * * *' # 2am UTC - workflow_dispatch: -permissions: - pull-requests: write -jobs: - upgrade_wrapper: - name: Execution - runs-on: ubuntu-latest - steps: - - name: Set up Git configuration - env: - TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - git config --global url."https://unused-username:${TOKEN}@github.com/".insteadOf "https://github.com/" - git config --global user.name 'github-actions[bot]' - git config --global user.email 'github-actions[bot]@users.noreply.github.com' - - name: Checkout - uses: actions/checkout@v4 - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - name: Set up Gradle - uses: gradle/gradle-build-action@v2 - - name: Upgrade Wrappers - run: ./gradlew clean upgradeGradleWrapperAll --continue -Porg.gradle.java.installations.auto-download=false - env: - WRAPPER_UPGRADE_GIT_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/merge-dependabot-pr.yml b/.github/workflows/merge-dependabot-pr.yml new file mode 100644 index 00000000000..b62c8167683 --- /dev/null +++ b/.github/workflows/merge-dependabot-pr.yml @@ -0,0 +1,30 @@ +name: Merge Dependabot PR + +on: pull_request_target + +run-name: Merge Dependabot PR ${{ github.ref_name }} + +permissions: + pull-requests: write + contents: write + +jobs: + merge-dependabot-pr: + runs-on: ubuntu-latest + if: github.event.pull_request.user.login == 'dependabot[bot]' + steps: + + - uses: actions/checkout@v5 + with: + show-progress: false + ref: ${{ github.event.pull_request.head.sha }} + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + + - name: Merge Dependabot pull request + run: gh pr merge ${{ github.event.pull_request.number }} --auto --rebase + env: + GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} diff --git a/.github/workflows/milestone-spring-releasetrain.yml b/.github/workflows/milestone-spring-releasetrain.yml deleted file mode 100644 index 0602ae8e73e..00000000000 --- a/.github/workflows/milestone-spring-releasetrain.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Check Milestone -on: - milestone: - types: [created, opened, edited] -env: - DUE_ON: ${{ github.event.milestone.due_on }} - TITLE: ${{ github.event.milestone.title }} -permissions: - contents: read -jobs: - spring-releasetrain-checks: - name: Check DueOn is on a Release Date - runs-on: ubuntu-latest - if: ${{ github.repository == 'spring-projects/spring-security' }} - permissions: - contents: none - steps: - - name: Print Milestone Being Checked - run: echo "Validating DueOn '$DUE_ON' for milestone '$TITLE'" - - name: Validate DueOn - if: env.DUE_ON != '' - run: | - export TOOL_VERSION=0.1.1 - wget "https://repo.maven.apache.org/maven2/io/spring/releasetrain/spring-release-train-tools/$TOOL_VERSION/spring-release-train-tools-$TOOL_VERSION.jar" - java -cp "spring-release-train-tools-$TOOL_VERSION.jar" io.spring.releasetrain.CheckMilestoneDueOnMain --dueOn "$DUE_ON" --expectedDayOfWeek MONDAY --expectedMondayCount 3 - send-notification: - name: Send Notification - needs: [ spring-releasetrain-checks ] - if: ${{ failure() || cancelled() }} - 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 }} diff --git a/.github/workflows/pr-build-workflow.yml b/.github/workflows/pr-build-workflow.yml deleted file mode 100644 index 2ebf86c76be..00000000000 --- a/.github/workflows/pr-build-workflow.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: PR Build - -on: pull_request - -permissions: - contents: read - -jobs: - build: - name: Build - runs-on: ubuntu-latest - if: ${{ github.repository == 'spring-projects/spring-security' }} - steps: - - uses: actions/checkout@v4 - - name: Set up gradle - uses: spring-io/spring-gradle-build-action@v2 - with: - java-version: '17' - distribution: 'temurin' - - name: Build with Gradle - run: ./gradlew clean build -PskipCheckExpectedBranchVersion --continue --scan - generate-docs: - name: Generate Docs - runs-on: ubuntu-latest - if: ${{ github.repository == 'spring-projects/spring-security' }} - steps: - - uses: actions/checkout@v4 - - name: Set up gradle - uses: spring-io/spring-gradle-build-action@v2 - with: - java-version: '17' - distribution: 'temurin' - - name: Run Antora - run: ./gradlew -PbuildSrc.skipTests=true :spring-security-docs:antora - - name: Upload Docs - id: upload - uses: actions/upload-artifact@v4 - with: - name: docs - path: docs/build/site - overwrite: true - send-notification: - name: Send Notification - needs: [ build, generate-docs ] - if: ${{ failure() && github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == 'spring-projects/spring-security' }} - 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 }} diff --git a/.github/workflows/release-scheduler.yml b/.github/workflows/release-scheduler.yml deleted file mode 100644 index 9f0045ba1ee..00000000000 --- a/.github/workflows/release-scheduler.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Release Scheduler -on: - schedule: - - cron: '15 15 * * MON' # Every Monday at 3:15pm UTC - workflow_dispatch: -permissions: read-all -jobs: - dispatch_scheduled_releases: - name: Dispatch scheduled releases - if: github.repository_owner == 'spring-projects' - strategy: - matrix: - # List of active maintenance branches. - branch: [ main, 6.5.x, 6.4.x, 6.3.x ] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - name: Dispatch - env: - GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} - run: gh workflow run update-scheduled-release-version.yml -r ${{ matrix.branch }} diff --git a/.github/workflows/update-antora-ui-spring.yml b/.github/workflows/update-antora-ui-spring.yml deleted file mode 100644 index f1309ed3012..00000000000 --- a/.github/workflows/update-antora-ui-spring.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Update Antora UI Spring - -on: - schedule: - - cron: '0 10 * * *' # Once per day at 10am UTC - workflow_dispatch: - -permissions: - pull-requests: write - issues: write - contents: write - -jobs: - update-antora-ui-spring: - runs-on: ubuntu-latest - name: Update on Supported Branches - strategy: - matrix: - branch: [ '5.8.x', '6.2.x', '6.3.x', 'main' ] - steps: - - uses: spring-io/spring-doc-actions/update-antora-spring-ui@e28269199d1d27975cf7f65e16d6095c555b3cd0 - name: Update - with: - docs-branch: ${{ matrix.branch }} - token: ${{ secrets.GITHUB_TOKEN }} - antora-file-path: 'docs/antora-playbook.yml' - update-antora-ui-spring-docs-build: - runs-on: ubuntu-latest - name: Update on docs-build - steps: - - uses: spring-io/spring-doc-actions/update-antora-spring-ui@e28269199d1d27975cf7f65e16d6095c555b3cd0 - name: Update - with: - docs-branch: 'docs-build' - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/update-scheduled-release-version.yml b/.github/workflows/update-scheduled-release-version.yml deleted file mode 100644 index 665b1b50b66..00000000000 --- a/.github/workflows/update-scheduled-release-version.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Update Scheduled Release Version - -on: - workflow_dispatch: # Manual trigger only. Triggered by release-scheduler.yml on main. - -permissions: - contents: read - -jobs: - update-scheduled-release-version: - name: Update Scheduled Release Version - uses: spring-io/spring-security-release-tools/.github/workflows/update-scheduled-release-version.yml@v1 - secrets: inherit - send-notification: - name: Send Notification - needs: [ update-scheduled-release-version ] - if: ${{ failure() || cancelled() }} - 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 }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 91b19aa1d2d..60bfae78748 100644 --- a/.gitignore +++ b/.gitignore @@ -1,33 +1,13 @@ -classes/ -target/ -*/src/*/java/META-INF -*/src/META-INF/ -*/src/*/java/META-INF/ -.classpath -.springBeans -.project -.DS_Store -.settings/ -.idea/* -out/ -bin/ -intellij/ -build/ -*.log -*.log.* -*.iml -*.ipr -*.iws -.gradle/ -atlassian-ide-plugin.xml -!etc/eclipse/.checkstyle -.checkstyle -s101plugin.state -.attach_pid* -.~lock.*# -.kotlin/ - -!.idea/checkstyle-idea.xml -!.idea/externalDependencies.xml - -node_modules +/.gradle/ +/.idea/* +/.settings/ +/.classpath +/.project +/build/ +/node_modules/ +/package-lock.json +/*.iml +/*.ipr +/*.iws +!/.idea/checkstyle-idea.xml +!/.idea/externalDependencies.xml diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index a69e23e6bc0..00000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "java.import.gradle.enabled": false -} diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc deleted file mode 100644 index b992d267bbd..00000000000 --- a/CONTRIBUTING.adoc +++ /dev/null @@ -1,146 +0,0 @@ -= Contributing to Spring Security - -First off, thank you for taking the time to contribute! :+1: :tada: - -== Table of Contents - -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> - -[[code-of-conduct]] -== Code of Conduct - -This project is governed by the https://github.com/spring-projects/.github/blob/main/CODE_OF_CONDUCT.md[Spring code of conduct]. -By participating you are expected to uphold this code. -Please report unacceptable behavior to spring-code-of-conduct@pivotal.io. - -[[how-to-contribute]] -== How to Contribute - -[[ask-questions]] -=== Ask Questions - -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]. -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. -Reading those discussions helps you to learn about the issue, and helps us to make a decision. - -[[find-an-issue]] -=== Find an Existing Issue - -There are many issues in Spring Security with the labels https://github.com/spring-projects/spring-security/issues?q=is%3Aissue+is%3Aopen+label%3A%22status%3A+ideal-for-contribution%22[`ideal-for-contribution`] or https://github.com/spring-projects/spring-security/issues?q=is%3Aissue+is%3Aopen+label%3A%22status%3A+first-timers-only%22[`first-timers-only`] that are a great way to contribute to a discussion or <>. -You can volunteer by commenting on these tickets, and we will assign them to you. - -[[create-an-issue]] -=== Create an Issue - -Reporting an issue or making a feature request is a great way to contribute. -Your feedback and the conversations that result from it provide a continuous flow of ideas. -However, before creating a ticket, please take the time to <> first. - -If you create an issue after a discussion on Stack Overflow, please provide a description in the issue instead of simply referring to Stack Overflow. -The issue tracker is an important place of record for design discussions and should be self-sufficient. - -Once you're ready, create an issue on https://github.com/spring-projects/spring-security/issues[GitHub]. - -Many issues are caused by subtle behavior, typos, and unintended configuration. -Creating a https://stackoverflow.com/help/minimal-reproducible-example[Minimal Reproducible Example] (starting with https://start.spring.io for example) of the problem helps the team quickly triage your issue and get to the core of the problem. - -We love contributors, and we may ask you to <>. - -[[issue-lifecycle]] -=== Issue Lifecycle - -When an issue is first created, it is flagged `waiting-for-triage` waiting for a team member to triage it. -Once the issue has been reviewed, the team may ask for further information if needed, and based on the findings, the issue is either assigned a target branch (or no branch if a feature) or is closed with a specific status. -The target branch is https://spring.io/projects/spring-security#support[the earliest supported branch] where <>. - -When a fix is ready, the issue is closed and may still be re-opened until the fix is released. -After that the issue will typically no longer be reopened. -In rare cases if the issue was not at all fixed, the issue may be re-opened. -In most cases however any follow-up reports will need to be created as new issues with a fresh description. - -[[build-from-source]] -=== Build from Source - -See https://github.com/spring-projects/spring-security/tree/main#building-from-source[Build from Source] for instructions on how to check out, build, and import the Spring Security source code into your IDE. - -[[code-style]] -=== Source Code Style - -The wiki pages https://github.com/spring-projects/spring-framework/wiki/Code-Style[Code Style] and https://github.com/spring-projects/spring-framework/wiki/IntelliJ-IDEA-Editor-Settings[IntelliJ IDEA Editor Settings] define the source file coding standards we use along with some IDEA editor settings we customize. - -Additionally, since Streams are https://github.com/spring-projects/spring-security/issues/7154[much slower] than `for` loops, please use them judiciously. -The team may ask you to change to a `for` loop if the given code is along a hot path. - -To format the code as well as check the style, run `./gradlew format && ./gradlew check`. - -[[submit-a-pull-request]] -=== Submit a Pull Request - -We are excited for your pull request! :heart: - -Please do your best to follow these steps. -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. -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 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. -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. -3. [[choose-a-branch]] Always check out the branch indicated in the milestone and submit pull requests against it (for example, for milestone `5.8.3` use the `5.8.x` branch). -If there is no milestone, choose `main`. -Once merged, the fix will be forwarded-ported to applicable branches including `main`. -4. [[create-a-local-branch]] Create a local branch -If this is for an issue, consider a branch name with the issue number, like `gh-22276`. -5. [[write-tests]] Add documentation and JUnit Tests for your changes. -6. [[update-copyright]] In all files you edited, if the copyright header is of the form 2002-20xx, update the final copyright year to the current year. -7. [[add-since]] If on `main`, add `@since` JavaDoc attributes to new public APIs that your PR adds -8. [[change-rnc]] If you are updating the XSD, please instead update the RNC file and then run `./gradlew :spring-security-config:rncToXsd`. -9. [[format-code]] For each commit, build the code using `./gradlew format && ./gradlew check`. -This command ensures the code meets most of <>; a notable exception is import order. -10. [[commit-atomically]] Choose the granularity of your commits consciously and squash commits that represent -multiple edits or corrections of the same logical change. -See https://git-scm.com/book/en/Git-Tools-Rewriting-History[Rewriting History section of Pro Git] for an overview of streamlining the commit history. -11. [[format-commit-messages]] Format commit messages using 55 characters for the subject line, 72 characters per line -for the description, followed by the issue fixed, for example, `Closes gh-22276`. -See the https://git-scm.com/book/en/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines[Commit Guidelines section of Pro Git] for best practices around commit messages, and use `git log` to see some examples. -Favor imperative tense over present tense (use "Fix" instead of "Fixes"); avoid past tense (use "Fix" instead of "Fixed"). -+ -[indent=0] ----- -Address NullPointerException - -Closes gh-22276 ----- -[[reference-issue]] -1. If there is a prior issue, reference the GitHub issue number in the description of the pull request. -+ -[indent=0] ----- -Closes gh-22276 ----- - -If accepted, your contribution may be heavily modified as needed prior to merging. -You will likely retain author attribution for your Git commits granted that the bulk of your changes remain intact. -You may also be asked to rework the submission. - -If asked to make corrections, simply push the changes against the same branch, and your pull request will be updated. -In other words, you do not need to create a new pull request when asked to make changes. -When it is time to merge, you'll be asked to squash your commits. - -==== Participate in Reviews - -Helping to review pull requests is another great way to contribute. -Your feedback can help to shape the implementation of new features. -When reviewing pull requests, however, please refrain from approving or rejecting a PR unless you are a core committer for Spring Security. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index 823c1c8e982..00000000000 --- a/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - https://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - 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. \ No newline at end of file diff --git a/README.adoc b/README.adoc index fdea7d89e26..75603e111a2 100644 --- a/README.adoc +++ b/README.adoc @@ -1,80 +1,478 @@ -image::https://badges.gitter.im/Join%20Chat.svg[Gitter,link=https://gitter.im/spring-projects/spring-security?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge] += Spring Security Docs Home +ifndef::env-github[:toc:] +ifdef::env-github[] +:important-caption: :exclamation: +:note-caption: :paperclip: +endif::[] -image:https://github.com/spring-projects/spring-security/actions/workflows/continuous-integration-workflow.yml/badge.svg?branch=main["Build Status", link="https://github.com/spring-projects/spring-security/actions/workflows/continuous-integration-workflow.yml"] +This README describes the processes and tools used to build the documentation for production as well as how to generate a local preview of the documentation. -image:https://img.shields.io/badge/Revved%20up%20by-Develocity-06A0CE?logo=Gradle&labelColor=02303A["Revved up by Develocity", link="https://ge.spring.io/scans?search.rootProjectNames=spring-security"] +== Overview -= Spring Security +The Spring Security reference docs are generated using Antora. +The Gradle Antora Plugin is used as the primary interface to Antora. -Spring Security provides security services for the https://docs.spring.io[Spring IO Platform]. Spring Security 6.0 requires Spring 6.0 as -a minimum and also requires Java 17. +You're viewing the playbook branch for the Spring Security project. +The playbook branch hosts the docs build used to build and publish the production docs site, and is thus the documentation home base. +If you're a docs developer, you'll mostly work in this branch. +If you're a writer, you'll mostly work in software branches. -For a detailed list of features and access to the latest release, please visit https://spring.io/projects[Spring projects]. +=== Layout -== Code of Conduct -Please see our https://github.com/spring-projects/.github/blob/main/CODE_OF_CONDUCT.md[code of conduct] +The playbook branch, named *docs-build*, hosts the primary documentation build. +The documentation itself is located in a dedicated subproject in each software branch (i.e., the docs are stored alongside the code). +Software branches, also referred to as release line branches or content branches, follow the pattern `major.minor.x` (e.g., 6.0.x), or `main` for the latest release line. +Software tags follow the pattern `major.minor.patch` (e.g., 6.0.1). +The latest version of the docs for each release line is always sourced from a tag. +The release line branches host the prerelease materials for the next version. -== Downloading Artifacts -See https://docs.spring.io/spring-security/reference/getting-spring-security.html[Getting Spring Security] for how to obtain Spring Security. +=== Builds -== Documentation -Be sure to read the https://docs.spring.io/spring-security/reference/[Spring Security Reference]. -Extensive JavaDoc for the Spring Security code is also available in the https://docs.spring.io/spring-security/site/docs/current/api/[Spring Security API Documentation]. +The build for the production site is stored at the root of the playbook branch. +This branch also holds the search crawler configuration and runner for the production site. +This build is only needed for building the production site (or for developing and testing the docs build itself). +The build for each content branch is located in a subproject, typically the folder named _docs_. +This is the build that authors use to preview the docs for a single version when writing content. -You may also want to check out https://docs.spring.io/spring-security/reference/whats-new.html[what's new in the latest release]. +Regardless of how the docs site is built, Antora is configured to run a separate command to compute the version of the docs per git reference. +This command is run by an Antora extension named Antora Collector. +Using Collector allows the version of the docs to be maintained centrally in the _gradle.properties_ file in each git reference. +The command also populates a collection of AsciiDoc attributes that provide access to software versions and resource URLs. +_The docs build will not work without Collector._ -== Quick Start -See https://docs.spring.io/spring-security/reference/servlet/getting-started.html[Hello Spring Security] to get started with a "Hello, World" application. +=== UI -== Building from Source -Spring Security uses a https://gradle.org[Gradle]-based build system. -In the instructions below, https://vimeo.com/34436402[`./gradlew`] is invoked from the root of the source tree and serves as -a cross-platform, self-contained bootstrap mechanism for the build. +The UI is developed in a separate project named https://github.com/spring-io/antora-ui-spring[antora-ui-spring]. +That project generates and publishes a UI bundle, which the docs build refers to using its public bundle URL. +There is one UI for all versions of the documentation. -=== Prerequisites -https://docs.github.com/en/get-started/quickstart/set-up-git[Git] and the https://www.oracle.com/java/technologies/downloads/#java17[JDK17 build]. +The UI only shows the latest version in each release line. +In order to access other versions that are published, the URL must be known in advance. -Be sure that your `JAVA_HOME` environment variable points to the `jdk-17` folder extracted from the JDK download. +[#usage] +== Usage -=== Check out sources -[indent=0] ----- -git clone git@github.com:spring-projects/spring-security.git ----- +To prepare your system for building the documentation, <>, then <>. +Then you can build the documentation in either the main branch or 6.0.x branch. -=== Install all `spring-*.jar` into your local Maven repository. +.Branch checkout instead of worktrees +[NOTE] +==== +If you prefer to set up your workspace without worktrees, complete the steps in <> and clone the project repository onto your computer. +Then, check out the desired branch and follow the instructions in each section starting from the `sdk env || sdk env install` step. +==== -[indent=0] ----- -./gradlew publishToMavenLocal ----- +[#prerequisites] +=== Prerequisites (everyone) -=== Compile and test; build all JARs, distribution zips, and docs +These instructions assume you already have basic tools on your system, including bash, zip, unzip, git, and curl. +In addition to these basic tools, you need https://sdkman.io/install[SDKMAN!] installed so that the correct JDK is set for each branch. -[indent=0] ----- -./gradlew build ----- +. Open your terminal and enter the following command: ++ +-- + $ curl -s "https://get.sdkman.io" | bash -The reference docs are not currently included in the distribution zip. -You can build the reference docs for this branch by running the following command: +This command downloads and installs SDKMAN! +Once installation is complete, you should see a command displayed in your terminal that will initiate SDKMAN!. +-- ----- -./gradlew :spring-security-docs:antora ----- +. Copy the command displayed in your terminal and run it. +In the following command, `$HOME` is the path unique to your computer (e.g., _home/username/.sdkman/bin/sdkman-init.sh_). -That command publishes the docs site to the `_docs/build/site_` directory. -The https://github.com/spring-projects/spring-security/tree/docs-build[playbook branch] describes how to build the reference docs in detail. + $ source "$HOME/.sdkman/bin/sdkman-init.sh" -Discover more commands with `./gradlew tasks`. +You'll use SDKMAN! in the following sections to install and switch to the JDK required for each branch. +Now you're ready to prepare your workspace. -== Getting Support -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. +[#prepare-workspace] +=== Prepare your workspace (everyone) -== Contributing -https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request[Pull requests] are welcome; see the https://github.com/spring-projects/spring-security/blob/main/CONTRIBUTING.adoc[contributor guidelines] for details. +Your workspace will be the folder that contains the git worktrees of the project. -== License -Spring Security is Open Source software released under the -https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license]. +. In your terminal, create a directory for the project and then change into that directory. + + $ mkdir spring-security + $ cd spring-security + +. Clone the project repository and create the worktree for the main branch. +Then change into the new *main* worktree. + + $ git clone https://github.com/spring-projects/spring-security main + $ cd main + +. Set up a worktree for the 6.0.x branch by running the following commands: + + $ git worktree add ../6.0.x 6.0.x --track + +. Repeat the last step for all release line branches (e.g., 5.8.x, 5.7.x, etc) you intend to work with. + +Now you're ready to build the docs in the main or 6.0.x branches. + +=== Build the documentation in the main branch (writers) + +NOTE: The instructions in this section assume you've completed the steps in <>. + +. First, make sure you're in the *main* worktree. +. Switch to the required JDK using SDKMAN! by running the following command: ++ +-- + $ sdk env || sdk env install + +SDKMAN! will switch to the required JDK, provisioning it first if it isn't already available on your machine. +-- + +. Generate the documentation with Antora using the following command: ++ +-- + $ ./gradlew -PbuildSrc.skipTests=true :spring-security-docs:antora + +You can also run this command directly from the _docs_ folder: + + $ cd docs + $ ../gradlew -PbuildSrc.skipTests=true antora + +This command will build the documentation for the *main* branch. +The Antora playbook is retrieved by the playbook provider from the *docs-build* branch. +The retrieved playbook file will be cached as _cached-antora-playbook.yml_. +-- + +. Navigate to the local file URI shown in the terminal to view the generated documentation. + +=== Build the documentation in the 6.0.x branch (writers) + +NOTE: The instructions in this section assume you've completed the steps in <>. + +. First, change to the *6.0.x* worktree. + + $ cd ../6.0.x + +. Switch to the required JDK using SDKMAN! by running the following command: ++ +-- + $ sdk env || sdk env install + +SDKMAN! will switch to the required JDK, provisioning it first if it isn't already available on your machine. +-- + +. Generate the documentation with the following command: ++ +-- + $ ./gradlew -PbuildSrc.skipTests=true :spring-security-docs:antora + +This command will build the documentation for the *6.0.x* branch. +The Antora playbook is retrieved by the playbook provider from the *docs-build* branch. +The retrieved playbook file will be cached as _cached-antora-playbook.yml_. +-- + +. Navigate to the local file URI shown in the terminal to view the generated documentation. + +=== Build the documentation for production (docs developer) + +NOTE: The instructions in this section assume you've completed the steps in <>. + +To build the project's production site, you'll set up a worktree for the *docs-build* branch of the repository. + +. To add a worktree, you have to be in the main worktree. +In your terminal, change to the *main* worktree if you aren't already in it. + + $ cd ../main + +. Run the following command to set up the worktree for the *docs-build* branch. +Then change into the new _docs-build_ directory. + + $ git worktree add ../docs-build docs-build --track + $ cd ../docs-build + +. Switch to the required JDK or install it. + + $ sdk env || sdk env install + +. Generate the documentation for the project's production site using the following command: ++ +-- + $ ./gradlew antora + +This command will build all of the documentation for the production site from the git repository on GitHub. + +(Optional) To build the documentation using the current clone, using any available worktrees, run the following command instead: + + $ ./gradlew antora --playbook local-antora-playbook.yml +-- + +. Navigate to the local file URI shown in the terminal to view the generated documentation. + +=== Content authoring + +We highly recommend relying on the https://intellij-asciidoc-plugin.ahus1.de/docs/users-guide/index.html[IntelliJ AsciiDoc Plugin] while writing. +It provides assistance and autocompletion for the AsciiDoc syntax, Antora resource IDs, attributes and keys set in the playbook, component version descriptor, etc. +It also provides single page preview. + +Once you've completed your edits, you'll build the branch locally to review and validate the changes. +*You don't need to build the entire site!* +*You don't need to create or edit the playbook or gradle.properties file either.* +Rather, you'll interface with the docs build, and it will automatically set up a playbook for your branch and manage any required extensions, right from the docs subproject in your software (content) branch. +See <> to learn how to build the docs. + +If the branch you modified has any AsciiDoc or Antora errors, they'll be printed to your terminal. +Once you've fixed any errors and reviewed your changes, submit a pull request to the relevant software branch. +If the change applies to multiple versions of the docs, you'll want to submit the pull request to the oldest active software branch. +The maintainer will then apply that change to each of the release line branches. + +== CI workflows + +CI workflows are run by GitHub Actions. +CI workflows are defined in YAML files in the _.github/workflows_ directory. +The CI workflows in the default (i.e., _main_) branch serve as the primary entry. +Corresponding CI workflows in a non-default branch may specialize the workflow for that branch. +However, the CI workflows in non-default branches do not receive all events and often have to be triggered. +A CI workflow must also be present in the default branch in order for it to appear in the list of workflows in the GitHub Actions web UI. + +CI workflows are triggered either by activity, on schedule, by the `gh workflow` call, or manually through the GitHub Actions web UI. +Scheduled workflows only run on the default branch (i.e., *main*). +However, a scheduled workflow may trigger a workflow in another branch using the `gh workflow` call. +Activity on a branch or tag is only picked up by workflows in that reference. +However, a workflow running in a branch or tag may trigger a workflow in another branch using the `gh workflow` call. + +There are two key CI workflows that pertain to the docs: + +* Deploy Docs (_.github/workflows/deploy-docs.yml_) +* Rebuild Search Index (_.github/workflows/rebuild-search-index.yml_) + +In both cases, the concrete steps are located in the CI workflow in the *docs-build* branch. +The CI workflows in the *main* branch only trigger the workflows in the *docs-build* branch. + +The production site is only deployed from the CI workflow in the *docs-build* branch. +Often times, activity in a git reference or a scheduled workflow will trigger the CI workflow in the *docs-build* branch. +Thus, this workflow is also present in each software branch to pick up on that activity. + +The production search index is built from the CI workflow in the *docs-build* branch. +This CI workflow is triggered once per day by the scheduler on the same workflow in the *main* branch. +This CI workflow must reside in the *main* branch in order to appear in the list of workflows in the CI branch so it can be triggered manually. + +Here's a list of activity that does and does not trigger the Deploy Docs workflow: + +pull request:: Does not trigger the Deploy Docs workflow. + +push to software branch:: Triggers the Deploy Docs workflow in that branch, which in turn triggers the Deploy Docs workflow in the *docs-build* branch. +Attempts to run the docs build as partial build, if applicable. + +push to docs-build branch:: Triggers the Deploy Docs workflow in that branch. +Runs the docs build as a full build. + +push tag:: Triggers the Deploy Docs workflow in that tag, which in turn triggers the Deploy Docs workflow in the *docs-build* branch. +Always runs the docs build as a full build. + +schedule:: Not configured for the Deploy Docs workflow. + +It's possible to trigger the Deploy Docs manually from the GitHub Actions web UI. +Be sure to select docs-build as the branch so that it will run a full build. +See <> for details. + +Note that updating the UI bundle does not currently trigger the Deploy Docs workflow, though it could be configured to do so. + +The Rebuild Search Index workflow is only triggered on a schedule, currently once per day. + +== Publishing + +The project in the *docs-build* branch supports publishing both full and partial builds of the production docs site. +The project uses a Gradle build to run Antora on the playbook file _antora-playbook.yml_ (i.e., `./gradlew antora`). +The production docs site is hosted on Linux server running Apache httpd. +Files are published over SSH to the server by the .github/actions/publish-docs.sh script. +A CDN (CloudFlare) caches URLs for a brief window of time. +The publish script attempts to invalidate the cache after publishing the files so new content is available immediately. + +=== Full build + +In a full build, the entire site is rebuilt from the content sources matched by the patterns listed in the playbook file. +The UI assets are also published when a full build is run. + +[#partial-builds] +=== Partial builds + +A partial build is a single version sources from a single git reference. +A partial build requested by git reference using the CI workflow variable *build-refname*. +Here's an example of how to trigger the CI workflow for a partial build: + + $ gh workflow run deploy-docs.yml --repo spring-projects/spring-security --ref docs-build -f build-refname=5.7.x + +The partial build is coordinated by the Antora Atlas extension and set up by the @springio/antora-extensions/partial-build-extension extension. +See https://github.com/spring-io/antora-extensions#partial-build[Partial Build] for a detailed explanation of the partial build extension and how to configure it. + +During a partial build, Atlas runs in same site mode, which means it creates relative links (rather than absolute links) to files imported from the site manifest. +This feature assumes that the built files will be reunited with the previously built files in the published site. +The @springio/antora-extensions/partial-build-extension reconfigures the playbook to run a partial build if the BUILD_REFNAME environment variable is set, reverting to a full build if it determines a partial build is not appropriate. + +During a partial build, only the version folder that was built is published to the web server. +Files in other folders are untouched. +The UI assets are not published when a partial build is run. + +[#trigger] +=== Trigger the documentation build workflow (docs developer) + +You can trigger the production document build using the Deploy Docs entry in the GitHub Actions web UI or using the https://cli.github.com/[GitHub CLI]. + +==== GitHub Actions web UI + +. In the GitHub Actions web UI, click the Deploy Docs entry. +. Click on the "Run workflow" menu. +.. *To trigger full build*, select the *docs-build* branch and click "Run workflow". +.. *To trigger a partial build*, specify a release line branch name in the input field labeled "Enter git refname to build" and click "Run workflow". + +==== GitHub CLI + +*To trigger full build*, start from within the cloned repository (ideally the playbook branch) and enter the `gh` command and options in the GitHub CLI: + + $ gh workflow run deploy-docs.yml --ref docs-build + +*To trigger a partial build*, enter the `gh` command and options to build a single version (based on the release line branch name): + + $ gh workflow run deploy-docs.yml --ref docs-build -f build-refname=5.7.x + +Run `gh help workflow run` to show the docs for this command and other examples of how to use it. + +If you're not running the `gh` command from within the cloned repository, you can specify the repository using the `--repo` CLI option (e.g., `--repo spring-projects/spring-security`). + +== Extensions + +The Spring Security docs have additional requirements above what Antora provides by default. +To fulfill these requirements, the docs build employs a handful of Antora and Asciidoctor extensions to build successfully. +You can't build the Spring Security docs using the base distribution of Antora. +Fortunately, this extra complexity is encapsulated in the Antora playbook and several distributed extensions. + +IMPORTANT: The order of Antora extensions in the playbook matters. +If the order is changed, it could result in files or metadata that an extension relies on not being available at the time it runs. + +For the most part, the extensions are retrieved from the npm package registry (npmjs.com). +There are also several local extensions in _lib/antora/extensions_. +The local extensions handle logic specific to this project and are only used for the production build. + +Below is a summary of the Antora and Asciidoctor extensions used in the docs build. + +=== Antora extensions + +@springio/antora-extensions/partial-build-extension (prod only):: Configures a partial build, when requested, by setting the `primary-site-url` and `primary-site-manifest-url` AsciiDoc attributes. +See <> for more information. +./lib/antora/extensions/inject-collector-config.js (prod only):: Injects configuration for Antora Collector into tags that predated Antora Collector being introduced. +See the next extension for details. +@antora/collector-extension:: Invokes a command (a Gradle task) to set the docs version from _gradle.properties_ and numerous AsciiDoc attributes that provide access to software versions and resource URLs. +The command that Antora Collector runs is essential for Antora to classify the docs properly. +./lib/antora/extensions/version-fix.js (prod only):: Fixes invalid metadata in _antora.yml_ and/or _gradle.properties_ in tags. +@antora/atlas-extension (prod only):: Generates the site manifest (_site-manifest.json_) and publishes it with the site. +Also coordinates the partial build when requested. +See <> for details. +@opendevise/antora-release-line-extension:: Abbreviates the version segment (in the URL) of the latest version in each release line from major.minor.patch to major.minor. +The version segment of the latest overall version is still abbreviated to empty string by Antora. +@springio/antora-extensions/tabs-migration-extension:: Migrates the tabs syntax from Spring Tabs to Asciidoctor Tabs. +See <> for details. +./lib/antora/extensions/publish-docsearch-config.js (prod only):: Publishes the docsearch config file to the production site so the indexer can use it. +See <> for details. + +=== Asciidoctor extensions + +@asciidoctor/tabs:: Enables the tabs block in AsciiDoc. +See <> and the https://github.com/asciidoctor/asciidoctor-tabs[Asciidoctor Tabs README] for details. +@springio/asciidoctor-extensions (prod only):: Provides various enhancements to the output generated by Asciidoctor, mostly around code blocks. +See https://github.com/spring-io/asciidoctor-extensions[Spring.io Asciidoctor Extensions] for an inventory of extensions and how to activate and configure them. + +[#tabs-migration] +== Tabs migration + +The Spring Security docs contain two variations of the tabs syntax, https://github.com/spring-io/spring-asciidoctor-backends#tabs[Spring Tabs] and https://github.com/asciidoctor/asciidoctor-tabs[Asciidoctor Tabs]. +Moving forward, Asciidoctor Tabs is the syntax that should be used. +However, since the Spring Security docs include content from tags that were written before Asciidoctor Tabs was introduced, the docs build must still be able to process the Spring Tabs syntax where it is used. + +When the docs build runs, the Spring Tabs are automatically converted to Asciidoctor Tabs by the @springio/antora-extensions/tabs-migration-extension extension. +Spring Tabs are never in the final output (unless the tabs migration extension is switched off). +This extension also has the ability to unwrap the example block that encloses adjacent tabs, when possible, so only the tabs block remains. +If Spring Tabs are not detected in a document, the migration will not run on that document. +See https://github.com/spring-io/antora-extensions#tabs-migration[Tabs Migration] for a detailed explanation of this extension and how to configure it. + +For the Spring Security docs, the tabs migration will always have to be used as long as there are tags in the build that contain Spring Tabs. +However, to reduce the amount of work the tabs migration extension has to do, the migration should be made permanent where possible. +Thus, we recommend making the migration permanent in release line branches that are active, and thus all future tags. + +Saving the result of the tabs migration is done one software branch at a time. +To start, switch to a branch and run the docs build in that branch (this will retrieve the Antora playbook). +Next, edit the _cached-antora-playbook.yml_ file and add `save: true` underneath the key `unwrap_example_block`. +This setting will save the migrated files back to their original location under the _docs_ folder. +Run the docs build in that branch again to apply the tabs migration. +Now commit the changed files. +Once that's done, the tabs migration won't have to run on any documents in that branch. + +[#search] +== Search + +The search component in the docs site is powered by Algolia DocSearch (specifically 2.6). +DocSearch is a documentation-oriented toolchain for using Algolia's search solution. +It provides both a crawler (aka scraper or indexer) and a search interface. +The search index is hosted on the Algolia platform and queried from the search interface via a web API. + +=== Client + +The search interface is integrated into the UI bundle and initialized when the page loads. +The search interface is configured using a collection of environment variables: `ALGOLIA_API_KEY`, `ALGOLIA_APP_ID`, and `ALGOLIA_INDEX_NAME`. +For now, these environment variables are defined in _build.gradle_ for the production build. +The search interface is only activated when all of these values are set. + +NOTE: The docsearch.js 2.6 package is marked as deprecated in npmjs.com. +However, the new client (@docsearch/js 3.x) has a completely different interaction model and search result display that's not compatible with customized client adapter currently in use. +In other words, switching to it means developing the customizations from scratch. +Even if that were to be done, the way the new client displays search results is over simplified. +The search results provided by docsearch.js 2.6 have proven to be clearer and easier to comprehend. + +=== Crawler + +The search index is created by the crawler component of DocSearch. +There are two steps involved. +First, the crawler must be configured. +Second, the crawler must be run with that configuration. + +==== Configure + +The behavior of the crawler is configured by a file name _docsearch-config.json_. +However, this file is not stored directly in the playbook branch. +Rather, it's generated from a template to account for the versions in the published site. + +The generation of the _docsearch-config.js_ file happens during the production build. +This file is generated by the Antora extension _lib/antora/extensions/publish-docsearch-config.js_. +The extension generates a docsearch config so that docsearch indexes the latest version in each release line. +To do so, the extension configures Handlebars to run using a model derived from information in Antora's content catalog. +It then evaluates the template at _.github/actions/docsearch-config.json.hbs_ to produce a file at the root of the generated site named _docsearch-config.js_. +That file is published as part of the site. + +==== Run + +The crawler is periodically run on the production site by the *Rebuild Search Index* workflow. +The crawler creates a fresh search index and replaces the previous one. +The name of the index is *spring-security-docs*. + +When the crawler runs, it downloads the _docsearch-config.json_ file from the production site and runs the docsearch action on it. + +NOTE: The crawler only needs to be run on files that are publicly accessible, so it makes sense that the configuration be located there too. + +In order to publish the search records (and thus create the index), the crawler must be configured using a collection of variables: `ALGOLIA_APPLICATION_ID` and `ALGOLIA_WRITE_KEY`. +These variables must be configured as secrets in GitHub Actions. +The index name is not required here as it is stored in the docsearch config file. + + + +== Maintenance + +The docs build requires regular maintenance. +Here's an inventory of the files or software versions to check and keep up to date. + +.Playbook branch (i.e., *docs-build*) +* Gradle Antora Plugin (_build.gradle_) +* GitHub Actions libraries (_.github/workflows/deploy-docs.yml_, _.github/workflows/rebuild-search-index.yml_) +* Java version (_.sdkmanrc_) +* Node.js packages (_build.gradle_ and _lib/antora/templates/per-branch-antora-playbook.yml_) +* Gradle Wrapper (_gradle/wrapper/gradle-wrapper.properties_) +* Content sources in playbook (_antora-playbook.yml_ and _local-antora-playbook.yml_; ideally use patterns to minimize maintenance) +* List of registered extensions (_antora-playbook.yml_, _local-antora-playbook.yml_, and _lib/antora/templates/per-branch-antora-playbook.yml_) + +.Content branches (e.g., *6.0.x*) +* Gradle Antora Plugin (_docs/spring-security-docs.gradle_) +* GitHub Actions libraries (_.github/workflows/deploy-docs.yml_, _.github/workflows/rebuild-search-index.yml_) + +Recall that the playbook used for the local docs preview in content branches is maintained in the *docs-build* branch in _lib/antora/templates/per-branch-antora-playbook.yml_. diff --git a/RELEASE.adoc b/RELEASE.adoc deleted file mode 100644 index 5730677a717..00000000000 --- a/RELEASE.adoc +++ /dev/null @@ -1,266 +0,0 @@ -= Release Process - -The release process for Spring Security is entirely automated via the https://github.com/spring-io/spring-security-release-tools/blob/main/release-plugin/README.adoc[Spring Security Release Plugin] and https://github.com/spring-io/spring-security-release-tools/tree/main/.github/workflows[reusable workflows]. -The following table outlines the steps that are taken by the automation. - -WARNING: The `5.8.x` branch does not have all of the improvements from the `6.x.x` branches. See "Status (5.8.x)" for which steps are still manual. - -In case of a failure, you can follow the links below to read about each step, which includes instructions for performing the step manually if applicable. -See <> for troubleshooting tips. - -[cols="1,1,1"] -|=== -| Step | Status (5.8.x) | Status (6.0.x+) - -| <> -| :white_check_mark: automated -| :white_check_mark: automated - -| <> -| :white_check_mark: automated -| :white_check_mark: automated - -| <> -| :white_check_mark: automated -| :white_check_mark: automated - -| <> -| :white_check_mark: automated -| :white_check_mark: automated - -| <> -| :white_check_mark: automated -| :white_check_mark: automated - -| <> -| :white_check_mark: automated -| :white_check_mark: automated - -| <> -| :white_check_mark: automated -| :white_check_mark: automated - -| <> -| :x: manual -| :white_check_mark: automated - -| <> -| :x: manual -| :white_check_mark: automated - -| <> -| :white_check_mark: automated -| :white_check_mark: automated - -| <> -| :white_check_mark: automated -| :white_check_mark: automated - -| <> -| :white_check_mark: automated -| :white_check_mark: automated - -| <> -| :x: manual -| :x: manual -|=== - -[#update-dependencies] -== Update dependencies - -Dependency versions are managed in the file xref:./gradle/libs.versions.toml[libs.versions.toml] and are automatically updated by xref:./.github/dependabot.yml[dependabot]. - -[#check-all-issues-are-closed] -== Check all issues are closed - -The first step of a release is to check if there are any open issues remaining in a milestone. - -NOTE: A scheduled release will not proceed if there are any open issues. - -TIP: If you need to prevent a release from occurring automatically, the easiest way to block a release is to add an unresolved issue to the milestone. - -The https://github.com/spring-io/spring-security-release-tools/blob/main/release-plugin/README.adoc#checkMilestoneHasNoOpenIssues[`checkMilestoneHasOpenIssues`] command will check if there are any open issues for the release. -Before running the command manually, replace the following values: - -* `` - Replace with the title of the milestone you are releasing now (i.e. 5.5.0-RC1) -* `` - Replace with a https://github.com/settings/tokens[GitHub personal access token] that has a scope of `public_repo`. This is optional since you are unlikely to reach the rate limit for such a simple check. - -[source,bash] ----- -./gradlew checkMilestoneHasOpenIssues -PnextVersion= -PgitHubAccessToken= ----- - -Alternatively, you can manually check using the https://github.com/spring-projects/spring-security/milestones[milestones] page. - -[#update-release-version] -== Update release version - -If all issues for the release are <>, the version number is automatically updated using the milestone title. -When performing this step manually, update the version number in `gradle.properties` for the release (for example `5.5.0`) and commit the change using the message "Release x.y.z". - -[#tag-release] -== Tag release - -The release will automatically be tagged using the milestone title. -It is not required to tag manually. -However, you can perform this step manually by running the following command: - -[source,bash] ----- -git tag 5.5.0 ----- - -[#push-release-commit] -== Push release commit - -During a scheduled release, the release commit will automatically be pushed to trigger a build. -If performing this step manually, you can push the commit and tag and GitHub actions will build and deploy the artifacts with the following command: - -[source,bash] ----- -git push --atomic origin main 5.5.0 ----- - -The build will automatically wait for artifacts to be released to Maven Central. -You can get notified manually when uploading is complete by running the following: - -[source,bash] ----- -./scripts/release/wait-for-done.sh 5.5.0 ----- - -[#build-locally] -== Build - -All checks will automatically be performed by the build prior to uploading the artifacts to Maven Central. -If something goes wrong, you can run the build locally using: - -[source,bash] ----- -./gradlew check ----- - -[#update-release-notes-on-github] -== Update release notes on GitHub - -Once the release has been uploaded to Maven Central, release notes will automatically be generated and a GitHub release will be created. -To do this manually, you can use the https://github.com/spring-io/spring-security-release-tools/blob/main/release-plugin/README.adoc#generateChangelog[`generateChangelog`] command to generate the release notes by replacing: - -* `` - Replace with the milestone you are releasing now (i.e. 5.5.0) - -[source,bash] ----- -./gradlew generateChangelog -PnextVersion= ----- - -Then copy the release notes to your clipboard (your mileage may vary with the following command): - -[source,bash] ----- -cat build/changelog/release-notes.md | xclip -selection clipboard ----- - -Finally, create the -https://github.com/spring-projects/spring-security/releases[release on -GitHub], associate it with the tag, and paste the generated notes. - -Alternatively, you can run the https://github.com/spring-io/spring-security-release-tools/blob/main/release-plugin/README.adoc#createGitHubRelease[`createGitHubRelease`] command to perform these steps automatically, replacing: - -* `` - Replace with the milestone you are releasing now (i.e. 5.5.0) -* `` - The name of the branch to be tagged (if the release commit has not already been tagged) -* `` - Replace with a https://github.com/settings/tokens[GitHub personal access token] that has a scope of `write:org` - -[source,bash] ----- -./gradlew createGitHubRelease -PnextVersion= -Pbranch= -PcreateRelease=true -PgitHubAccessToken= ----- - -[#update-version-on-project-page] -== Update version on project page - -The build will automatically update the project versions on https://spring.io/projects/spring-security#learn. -To do this manually, you can use the https://github.com/spring-io/spring-security-release-tools/blob/main/release-plugin/README.adoc#createSaganRelease[`createSaganRelease`] and https://github.com/spring-io/spring-security-release-tools/blob/main/release-plugin/README.adoc#deleteSaganRelease[`deleteSaganRelease`] commands using the following parameters: - -* `` - Replace with the milestone you are releasing now (i.e. 5.5.0) -* `` - Replace with the previous release which will be removed from the listed versions (i.e. 5.5.0-RC1) -* `` - Replace with a https://github.com/settings/tokens[GitHub personal access token] that has a scope of `read:org` as https://spring.io/restdocs/index.html#authentication[documented for spring.io api] - -[source,bash] ----- -./gradlew createSaganRelease deleteSaganRelease -PnextVersion= -PpreviousVersion= -PgitHubAccessToken= ----- - -Alternatively, you can log into Contentful and update the versions manually on the Spring Security project page. - -[#close-create-milestone] -== Close / Create milestone - -The release milestone will be automatically closed once the release is complete. -To proceed manually, perform the following steps: - -1. Visit https://github.com/spring-projects/spring-security/milestones[GitHub -Milestones] and create a new milestone for the next release version -2. Move any open issues from the existing milestone you just released to the new milestone -3. Close the milestone for the release - -NOTE: Remember that scheduled releases <> if there are still open issues in the milestone. - -[#announce-release-on-slack] -== Announce release on Slack - -The release will automatically be announced on Slack. -If proceeding manually, announce the release on Slack in the channel https://pivotal.slack.com/messages/spring-release[#spring-release], including the keyword `+spring-security-announcing+` in the message. -Something like: - -.... -spring-security-announcing `5.5.0` is available now -.... - -[#update-to-next-development-version] -== Update to next development version - -After the release is complete and artifacts have been uploaded to Maven Central, the build will automatically update to the next development version, commit and push. -If proceeding manually, update the version in `gradle.properties` to the next `+SNAPSHOT+` version with the commit message "Next development version" and then push. - -[#announce-release-on-other-channels] -== Announce release on other channels - -* Create a blog post on Contentful -* Tweet from https://twitter.com/springsecurity[@SpringSecurity] - -[[frequently-asked-questions]] -== Frequently Asked Questions - -*When should I update dependencies manually?* Dependencies should be updated at the latest the end of the week prior to the release. This is usually the Friday following the 2nd Monday of the month (counting from the first week with a Monday). When in doubt, check the https://github.com/spring-projects/spring-security/milestones[milestones] page for release due dates. - -*When do scheduled releases occur?* Automated releases are scheduled to occur at *3:15 PM UTC* on the *3rd Monday of the month* (counting from the first week with a Monday). - -[NOTE] -The scheduled release process currently runs every Monday but only releases when a release is due. See the performed checks below for more information. - -The automated release process occurs on the following branches: - -* `main` -* `6.2.x` -* `6.1.x` -* `6.0.x` (commercial only) -* `5.8.x` - -For each of the above branches, the automated process performs the following checks before proceeding with the release: - -1. _Check if the milestone is due today._ This check compares the current (SNAPSHOT) version of the branch with available milestones and chooses the first match (sorted alphabetically). If the due date on the matched milestone is *not* today, the process stops. -2. _Check if all issues are closed._ This check uses the milestone from the previous step and looks for open issues. If any open issues are found, the process stops. - -[IMPORTANT] -You should ensure all issues are closed or moved to another milestone prior to a scheduled release. - -If the above checks pass, the version number is updated (in `gradle.properties`) and a commit is pushed to trigger the CI process. - -*How do I trigger a release manually?* You can trigger a release manually in two ways: - -1. Trigger a release for a particular branch via https://github.com/spring-projects/spring-security/actions/workflows/update-scheduled-release-version.yml[`update-scheduled-release-version.yml`] on the desired branch. The above checks are performed for that branch, and the release will proceed if all checks pass. _This is the recommended way to trigger a release that did not pass the above checks during a regularly scheduled release._ -2. Trigger releases for all branches via https://github.com/spring-projects/spring-security/actions/workflows/release-scheduler.yml[`release-scheduler.yml`] on the `main` branch. The above checks are performed for each branch, and only releases that pass all checks will proceed. - -*When should additional manual steps be performed?* All other automated steps listed above occur during the normal CI process. Additional manual steps can be performed at any time once the builds pass and releases are finished. - -*What if something goes wrong?* If the normal CI process fails, you can retry by re-running the failed jobs with the "Re-run failed jobs" option in GitHub Actions. If changes are required, you should revert the "Release x.y.z" commit, delete the tag, and proceed manually. diff --git a/access/spring-security-access.gradle b/access/spring-security-access.gradle deleted file mode 100644 index f5747c66860..00000000000 --- a/access/spring-security-access.gradle +++ /dev/null @@ -1,49 +0,0 @@ -apply plugin: 'io.spring.convention.spring-module' - -dependencies { - management platform(project(":spring-security-dependencies")) - api project(':spring-security-crypto') - api project(':spring-security-core') - api 'org.springframework:spring-aop' - api 'org.springframework:spring-beans' - api 'org.springframework:spring-context' - api 'org.springframework:spring-core' - api 'org.springframework:spring-expression' - api 'io.micrometer:micrometer-observation' - - optional project(':spring-security-acl') - optional project(':spring-security-messaging') - optional project(':spring-security-web') - optional 'org.springframework:spring-websocket' - optional 'com.fasterxml.jackson.core:jackson-databind' - optional 'io.micrometer:context-propagation' - optional 'io.projectreactor:reactor-core' - optional 'jakarta.annotation:jakarta.annotation-api' - optional 'org.aspectj:aspectjrt' - optional 'org.springframework:spring-jdbc' - optional 'org.springframework:spring-tx' - optional 'org.jetbrains.kotlinx:kotlinx-coroutines-reactor' - - provided 'jakarta.servlet:jakarta.servlet-api' - - testImplementation project(path : ':spring-security-web', configuration : 'tests') - testImplementation 'commons-collections:commons-collections' - testImplementation 'io.projectreactor:reactor-test' - testImplementation "org.assertj:assertj-core" - testImplementation "org.junit.jupiter:junit-jupiter-api" - testImplementation "org.junit.jupiter:junit-jupiter-params" - testImplementation "org.junit.jupiter:junit-jupiter-engine" - testImplementation "org.mockito:mockito-core" - testImplementation "org.mockito:mockito-junit-jupiter" - testImplementation "org.springframework:spring-core-test" - testImplementation "org.springframework:spring-test" - testImplementation 'org.skyscreamer:jsonassert' - testImplementation 'org.springframework:spring-test' - testImplementation 'org.jetbrains.kotlin:kotlin-reflect' - testImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8' - testImplementation 'io.mockk:mockk' - - testRuntimeOnly 'org.hsqldb:hsqldb' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' -} - diff --git a/access/src/main/java/org/springframework/security/access/AccessDecisionManager.java b/access/src/main/java/org/springframework/security/access/AccessDecisionManager.java deleted file mode 100644 index 7163b3634b8..00000000000 --- a/access/src/main/java/org/springframework/security/access/AccessDecisionManager.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access; - -import java.util.Collection; - -import org.springframework.security.authentication.InsufficientAuthenticationException; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.Authentication; - -/** - * Makes a final access control (authorization) decision. - * - * @author Ben Alex - * @deprecated Use {@link AuthorizationManager} instead - */ -@Deprecated -public interface AccessDecisionManager { - - /** - * Resolves an access control decision for the passed parameters. - * @param authentication the caller invoking the method (not null) - * @param object the secured object being called - * @param configAttributes the configuration attributes associated with the secured - * object being invoked - * @throws AccessDeniedException if access is denied as the authentication does not - * hold a required authority or ACL privilege - * @throws InsufficientAuthenticationException if access is denied as the - * authentication does not provide a sufficient level of trust - */ - void decide(Authentication authentication, Object object, Collection configAttributes) - throws AccessDeniedException, InsufficientAuthenticationException; - - /** - * Indicates whether this AccessDecisionManager is able to process - * authorization requests presented with the passed ConfigAttribute. - *

- * This allows the AbstractSecurityInterceptor to check every - * configuration attribute can be consumed by the configured - * AccessDecisionManager and/or RunAsManager and/or - * AfterInvocationManager. - *

- * @param attribute a configuration attribute that has been configured against the - * AbstractSecurityInterceptor - * @return true if this AccessDecisionManager can support the passed - * configuration attribute - */ - boolean supports(ConfigAttribute attribute); - - /** - * Indicates whether the AccessDecisionManager implementation is able to - * provide access control decisions for the indicated secured object type. - * @param clazz the class that is being queried - * @return true if the implementation can process the indicated class - */ - boolean supports(Class clazz); - -} diff --git a/access/src/main/java/org/springframework/security/access/AccessDecisionVoter.java b/access/src/main/java/org/springframework/security/access/AccessDecisionVoter.java deleted file mode 100644 index 8666352a51f..00000000000 --- a/access/src/main/java/org/springframework/security/access/AccessDecisionVoter.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access; - -import java.util.Collection; - -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.Authentication; - -/** - * Indicates a class is responsible for voting on authorization decisions. - *

- * The coordination of voting (ie polling {@code AccessDecisionVoter}s, tallying their - * responses, and making the final authorization decision) is performed by an - * {@link org.springframework.security.access.AccessDecisionManager}. - * - * @author Ben Alex - * @deprecated Use {@link AuthorizationManager} instead - */ -@Deprecated -public interface AccessDecisionVoter { - - int ACCESS_GRANTED = 1; - - int ACCESS_ABSTAIN = 0; - - int ACCESS_DENIED = -1; - - /** - * Indicates whether this {@code AccessDecisionVoter} is able to vote on the passed - * {@code ConfigAttribute}. - *

- * This allows the {@code AbstractSecurityInterceptor} to check every configuration - * attribute can be consumed by the configured {@code AccessDecisionManager} and/or - * {@code RunAsManager} and/or {@code AfterInvocationManager}. - * @param attribute a configuration attribute that has been configured against the - * {@code AbstractSecurityInterceptor} - * @return true if this {@code AccessDecisionVoter} can support the passed - * configuration attribute - */ - boolean supports(ConfigAttribute attribute); - - /** - * Indicates whether the {@code AccessDecisionVoter} implementation is able to provide - * access control votes for the indicated secured object type. - * @param clazz the class that is being queried - * @return true if the implementation can process the indicated class - */ - boolean supports(Class clazz); - - /** - * Indicates whether or not access is granted. - *

- * The decision must be affirmative ({@code ACCESS_GRANTED}), negative ( - * {@code ACCESS_DENIED}) or the {@code AccessDecisionVoter} can abstain ( - * {@code ACCESS_ABSTAIN}) from voting. Under no circumstances should implementing - * classes return any other value. If a weighting of results is desired, this should - * be handled in a custom - * {@link org.springframework.security.access.AccessDecisionManager} instead. - *

- * Unless an {@code AccessDecisionVoter} is specifically intended to vote on an access - * control decision due to a passed method invocation or configuration attribute - * parameter, it must return {@code ACCESS_ABSTAIN}. This prevents the coordinating - * {@code AccessDecisionManager} from counting votes from those - * {@code AccessDecisionVoter}s without a legitimate interest in the access control - * decision. - *

- * Whilst the secured object (such as a {@code MethodInvocation}) is passed as a - * parameter to maximise flexibility in making access control decisions, implementing - * classes should not modify it or cause the represented invocation to take place (for - * example, by calling {@code MethodInvocation.proceed()}). - * @param authentication the caller making the invocation - * @param object the secured object being invoked - * @param attributes the configuration attributes associated with the secured object - * @return either {@link #ACCESS_GRANTED}, {@link #ACCESS_ABSTAIN} or - * {@link #ACCESS_DENIED} - */ - int vote(Authentication authentication, S object, Collection attributes); - -} diff --git a/access/src/main/java/org/springframework/security/access/AfterInvocationProvider.java b/access/src/main/java/org/springframework/security/access/AfterInvocationProvider.java deleted file mode 100644 index bae49ad37fa..00000000000 --- a/access/src/main/java/org/springframework/security/access/AfterInvocationProvider.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access; - -import java.util.Collection; - -import org.springframework.security.access.intercept.AfterInvocationProviderManager; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.Authentication; - -/** - * Indicates a class is responsible for participating in an - * {@link AfterInvocationProviderManager} decision. - * - * @author Ben Alex - * @see org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor - * @see org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor - * @deprecated Use delegation with {@link AuthorizationManager} - */ -@Deprecated -public interface AfterInvocationProvider { - - Object decide(Authentication authentication, Object object, Collection attributes, - Object returnedObject) throws AccessDeniedException; - - /** - * Indicates whether this AfterInvocationProvider is able to participate - * in a decision involving the passed ConfigAttribute. - *

- * This allows the AbstractSecurityInterceptor to check every - * configuration attribute can be consumed by the configured - * AccessDecisionManager and/or RunAsManager and/or - * AccessDecisionManager. - *

- * @param attribute a configuration attribute that has been configured against the - * AbstractSecurityInterceptor - * @return true if this AfterInvocationProvider can support the passed - * configuration attribute - */ - boolean supports(ConfigAttribute attribute); - - /** - * Indicates whether the AfterInvocationProvider is able to provide - * "after invocation" processing for the indicated secured object type. - * @param clazz the class of secure object that is being queried - * @return true if the implementation can process the indicated class - */ - boolean supports(Class clazz); - -} diff --git a/access/src/main/java/org/springframework/security/access/ConfigAttribute.java b/access/src/main/java/org/springframework/security/access/ConfigAttribute.java deleted file mode 100644 index 1f8ef11ce8e..00000000000 --- a/access/src/main/java/org/springframework/security/access/ConfigAttribute.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access; - -import java.io.Serializable; - -import org.jspecify.annotations.NullUnmarked; - -import org.springframework.security.access.intercept.RunAsManager; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.annotation.SecurityAnnotationScanner; - -/** - * Stores a security system related configuration attribute. - * - *

- * When an - * {@link org.springframework.security.access.intercept.AbstractSecurityInterceptor} is - * set up, a list of configuration attributes is defined for secure object patterns. These - * configuration attributes have special meaning to a {@link RunAsManager}, - * {@link AccessDecisionManager} or AccessDecisionManager delegate. - * - *

- * Stored at runtime with other ConfigAttributes for the same secure object - * target. - * - * @author Ben Alex - * @deprecated In modern Spring Security APIs, each API manages its own configuration - * context. As such there is no direct replacement for this interface. In the case of - * method security, please see {@link SecurityAnnotationScanner} and - * {@link AuthorizationManager}. In the case of channel security, please see - * {@code HttpsRedirectFilter}. In the case of web security, please see - * {@link AuthorizationManager}. - */ -@Deprecated -@NullUnmarked -public interface ConfigAttribute extends Serializable { - - /** - * If the ConfigAttribute can be represented as a String and - * that String is sufficient in precision to be relied upon as a - * configuration parameter by a {@link RunAsManager}, {@link AccessDecisionManager} or - * AccessDecisionManager delegate, this method should return such a - * String. - *

- * If the ConfigAttribute cannot be expressed with sufficient precision - * as a String, null should be returned. Returning - * null will require any relying classes to specifically support the - * ConfigAttribute implementation, so returning null should - * be avoided unless actually required. - * @return a representation of the configuration attribute (or null if - * the configuration attribute cannot be expressed as a String with - * sufficient precision). - */ - String getAttribute(); - -} diff --git a/access/src/main/java/org/springframework/security/access/SecurityConfig.java b/access/src/main/java/org/springframework/security/access/SecurityConfig.java deleted file mode 100644 index 7f471d1ef75..00000000000 --- a/access/src/main/java/org/springframework/security/access/SecurityConfig.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access; - -import java.io.Serial; -import java.util.ArrayList; -import java.util.List; - -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.annotation.SecurityAnnotationScanner; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -/** - * Stores a {@link ConfigAttribute} as a String. - * - * @author Ben Alex - * @deprecated In modern Spring Security APIs, each API manages its own configuration - * context. As such there is no direct replacement for this interface. In the case of - * method security, please see {@link SecurityAnnotationScanner} and - * {@link AuthorizationManager}. In the case of channel security, please see - * {@code HttpsRedirectFilter}. In the case of web security, please see - * {@link AuthorizationManager}. - */ -@Deprecated -public class SecurityConfig implements ConfigAttribute { - - @Serial - private static final long serialVersionUID = -7138084564199804304L; - - private final String attrib; - - public SecurityConfig(String config) { - Assert.hasText(config, "You must provide a configuration attribute"); - this.attrib = config; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof ConfigAttribute attr) { - return this.attrib.equals(attr.getAttribute()); - } - return false; - } - - @Override - public String getAttribute() { - return this.attrib; - } - - @Override - public int hashCode() { - return this.attrib.hashCode(); - } - - @Override - public String toString() { - return this.attrib; - } - - public static List createListFromCommaDelimitedString(String access) { - return createList(StringUtils.commaDelimitedListToStringArray(access)); - } - - public static List createList(String... attributeNames) { - Assert.notNull(attributeNames, "You must supply an array of attribute names"); - List attributes = new ArrayList<>(attributeNames.length); - for (String attribute : attributeNames) { - attributes.add(new SecurityConfig(attribute.trim())); - } - return attributes; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/SecurityMetadataSource.java b/access/src/main/java/org/springframework/security/access/SecurityMetadataSource.java deleted file mode 100644 index 6232c651a23..00000000000 --- a/access/src/main/java/org/springframework/security/access/SecurityMetadataSource.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access; - -import java.util.Collection; - -import org.springframework.aop.framework.AopInfrastructureBean; -import org.springframework.security.access.intercept.AbstractSecurityInterceptor; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.annotation.SecurityAnnotationScanner; - -/** - * Implemented by classes that store and can identify the {@link ConfigAttribute}s that - * applies to a given secure object invocation. - * - * @author Ben Alex - * @deprecated In modern Spring Security APIs, each API manages its own configuration - * context. As such there is no direct replacement for this interface. In the case of - * method security, please see {@link SecurityAnnotationScanner} and - * {@link AuthorizationManager}. In the case of channel security, please see - * {@code HttpsRedirectFilter}. In the case of web security, please see - * {@link AuthorizationManager}. - */ -@Deprecated -public interface SecurityMetadataSource extends AopInfrastructureBean { - - /** - * Accesses the {@code ConfigAttribute}s that apply to a given secure object. - * @param object the object being secured - * @return the attributes that apply to the passed in secured object. Should return an - * empty collection if there are no applicable attributes. - * @throws IllegalArgumentException if the passed object is not of a type supported by - * the SecurityMetadataSource implementation - */ - Collection getAttributes(Object object) throws IllegalArgumentException; - - /** - * If available, returns all of the {@code ConfigAttribute}s defined by the - * implementing class. - *

- * This is used by the {@link AbstractSecurityInterceptor} to perform startup time - * validation of each {@code ConfigAttribute} configured against it. - * @return the {@code ConfigAttribute}s or {@code null} if unsupported - */ - Collection getAllConfigAttributes(); - - /** - * Indicates whether the {@code SecurityMetadataSource} implementation is able to - * provide {@code ConfigAttribute}s for the indicated secure object type. - * @param clazz the class that is being queried - * @return true if the implementation can process the indicated class - */ - boolean supports(Class clazz); - -} diff --git a/access/src/main/java/org/springframework/security/access/annotation/AnnotationMetadataExtractor.java b/access/src/main/java/org/springframework/security/access/annotation/AnnotationMetadataExtractor.java deleted file mode 100644 index 4414443e766..00000000000 --- a/access/src/main/java/org/springframework/security/access/annotation/AnnotationMetadataExtractor.java +++ /dev/null @@ -1,40 +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.access.annotation; - -import java.lang.annotation.Annotation; -import java.util.Collection; - -import org.springframework.security.access.ConfigAttribute; - -/** - * Strategy to process a custom security annotation to extract the relevant - * {@code ConfigAttribute}s for securing a method. - *

- * Used by {@code SecuredAnnotationSecurityMetadataSource}. - * - * @author Luke Taylor - * @deprecated Used only by now-deprecated classes. Consider - * {@link org.springframework.security.authorization.method.SecuredAuthorizationManager} - * for `@Secured` methods. - */ -@Deprecated -public interface AnnotationMetadataExtractor { - - Collection extractAttributes(A securityAnnotation); - -} diff --git a/access/src/main/java/org/springframework/security/access/annotation/Jsr250MethodSecurityMetadataSource.java b/access/src/main/java/org/springframework/security/access/annotation/Jsr250MethodSecurityMetadataSource.java deleted file mode 100644 index fc19138609a..00000000000 --- a/access/src/main/java/org/springframework/security/access/annotation/Jsr250MethodSecurityMetadataSource.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.annotation; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import jakarta.annotation.security.DenyAll; -import jakarta.annotation.security.PermitAll; -import jakarta.annotation.security.RolesAllowed; -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.method.AbstractFallbackMethodSecurityMetadataSource; - -/** - * Sources method security metadata from major JSR 250 security annotations. - * - * @author Ben Alex - * @since 2.0 - * @deprecated Use - * {@link org.springframework.security.authorization.method.Jsr250AuthorizationManager} - * instead - */ -@NullUnmarked -@Deprecated -public class Jsr250MethodSecurityMetadataSource extends AbstractFallbackMethodSecurityMetadataSource { - - private String defaultRolePrefix = "ROLE_"; - - /** - *

- * Sets the default prefix to be added to {@link RolesAllowed}. For example, if - * {@code @RolesAllowed("ADMIN")} or {@code @RolesAllowed("ADMIN")} is used, then the - * role ROLE_ADMIN will be used when the defaultRolePrefix is "ROLE_" (default). - *

- * - *

- * If null or empty, then no default role prefix is used. - *

- * @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_". - */ - public void setDefaultRolePrefix(String defaultRolePrefix) { - this.defaultRolePrefix = defaultRolePrefix; - } - - @Override - protected Collection findAttributes(Class clazz) { - return processAnnotations(clazz.getAnnotations()); - } - - @Override - protected Collection findAttributes(Method method, Class targetClass) { - return processAnnotations(AnnotationUtils.getAnnotations(method)); - } - - @Override - public @Nullable Collection getAllConfigAttributes() { - return null; - } - - private @Nullable List processAnnotations(Annotation @Nullable [] annotations) { - if (annotations == null || annotations.length == 0) { - return null; - } - List attributes = new ArrayList<>(); - for (Annotation annotation : annotations) { - if (annotation instanceof DenyAll) { - attributes.add(Jsr250SecurityConfig.DENY_ALL_ATTRIBUTE); - return attributes; - } - if (annotation instanceof PermitAll) { - attributes.add(Jsr250SecurityConfig.PERMIT_ALL_ATTRIBUTE); - return attributes; - } - if (annotation instanceof RolesAllowed ra) { - - for (String allowed : ra.value()) { - String defaultedAllowed = getRoleWithDefaultPrefix(allowed); - attributes.add(new Jsr250SecurityConfig(defaultedAllowed)); - } - return attributes; - } - } - return null; - } - - private String getRoleWithDefaultPrefix(String role) { - if (role == null) { - return role; - } - if (this.defaultRolePrefix == null || this.defaultRolePrefix.length() == 0) { - return role; - } - if (role.startsWith(this.defaultRolePrefix)) { - return role; - } - return this.defaultRolePrefix + role; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/annotation/Jsr250SecurityConfig.java b/access/src/main/java/org/springframework/security/access/annotation/Jsr250SecurityConfig.java deleted file mode 100644 index 5a584cdbf55..00000000000 --- a/access/src/main/java/org/springframework/security/access/annotation/Jsr250SecurityConfig.java +++ /dev/null @@ -1,44 +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.access.annotation; - -import jakarta.annotation.security.DenyAll; -import jakarta.annotation.security.PermitAll; - -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; - -/** - * Security config applicable as a JSR 250 annotation attribute. - * - * @author Ryan Heaton - * @since 2.0 - * @deprecated Use {@link AuthorizationManagerBeforeMethodInterceptor#jsr250()} instead - */ -@Deprecated -@SuppressWarnings("serial") -public class Jsr250SecurityConfig extends SecurityConfig { - - public static final Jsr250SecurityConfig PERMIT_ALL_ATTRIBUTE = new Jsr250SecurityConfig(PermitAll.class.getName()); - - public static final Jsr250SecurityConfig DENY_ALL_ATTRIBUTE = new Jsr250SecurityConfig(DenyAll.class.getName()); - - public Jsr250SecurityConfig(String role) { - super(role); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/annotation/Jsr250Voter.java b/access/src/main/java/org/springframework/security/access/annotation/Jsr250Voter.java deleted file mode 100644 index fadd5d1ff05..00000000000 --- a/access/src/main/java/org/springframework/security/access/annotation/Jsr250Voter.java +++ /dev/null @@ -1,92 +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.access.annotation; - -import java.util.Collection; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; - -/** - * Voter on JSR-250 configuration attributes. - * - * @author Ryan Heaton - * @since 2.0 - * @deprecated Use - * {@link org.springframework.security.authorization.method.Jsr250AuthorizationManager} - * instead - */ -@Deprecated -public class Jsr250Voter implements AccessDecisionVoter { - - /** - * The specified config attribute is supported if its an instance of a - * {@link Jsr250SecurityConfig}. - * @param configAttribute The config attribute. - * @return whether the config attribute is supported. - */ - @Override - public boolean supports(ConfigAttribute configAttribute) { - return configAttribute instanceof Jsr250SecurityConfig; - } - - /** - * All classes are supported. - * @param clazz the class. - * @return true - */ - @Override - public boolean supports(Class clazz) { - return true; - } - - /** - * Votes according to JSR 250. - *

- * If no JSR-250 attributes are found, it will abstain, otherwise it will grant or - * deny access based on the attributes that are found. - * @param authentication The authentication object. - * @param object The access object. - * @param definition The configuration definition. - * @return The vote. - */ - @Override - public int vote(Authentication authentication, Object object, Collection definition) { - boolean jsr250AttributeFound = false; - for (ConfigAttribute attribute : definition) { - if (Jsr250SecurityConfig.PERMIT_ALL_ATTRIBUTE.equals(attribute)) { - return ACCESS_GRANTED; - } - if (Jsr250SecurityConfig.DENY_ALL_ATTRIBUTE.equals(attribute)) { - return ACCESS_DENIED; - } - if (supports(attribute)) { - jsr250AttributeFound = true; - // Attempt to find a matching granted authority - for (GrantedAuthority authority : authentication.getAuthorities()) { - if (attribute.getAttribute().equals(authority.getAuthority())) { - return ACCESS_GRANTED; - } - } - } - } - return jsr250AttributeFound ? ACCESS_DENIED : ACCESS_ABSTAIN; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataSource.java b/access/src/main/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataSource.java deleted file mode 100644 index 47f33288fd2..00000000000 --- a/access/src/main/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataSource.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.annotation; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.core.GenericTypeResolver; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.access.method.AbstractFallbackMethodSecurityMetadataSource; -import org.springframework.util.Assert; - -/** - * Sources method security metadata from Spring Security's {@link Secured} annotation. - *

- * Can also be used with custom security annotations by injecting an - * {@link AnnotationMetadataExtractor}. The annotation type will then be obtained from the - * generic parameter type supplied to this interface - * - * @author Ben Alex - * @author Luke Taylor - * @deprecated Use - * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor#secured} - */ -@NullUnmarked -@Deprecated -@SuppressWarnings({ "unchecked" }) -public class SecuredAnnotationSecurityMetadataSource extends AbstractFallbackMethodSecurityMetadataSource { - - private AnnotationMetadataExtractor annotationExtractor; - - private @Nullable Class annotationType; - - public SecuredAnnotationSecurityMetadataSource() { - this(new SecuredAnnotationMetadataExtractor()); - } - - public SecuredAnnotationSecurityMetadataSource(AnnotationMetadataExtractor annotationMetadataExtractor) { - Assert.notNull(annotationMetadataExtractor, "annotationMetadataExtractor cannot be null"); - this.annotationExtractor = annotationMetadataExtractor; - this.annotationType = (Class) GenericTypeResolver - .resolveTypeArgument(this.annotationExtractor.getClass(), AnnotationMetadataExtractor.class); - Assert.notNull(this.annotationType, () -> this.annotationExtractor.getClass().getName() - + " must supply a generic parameter for AnnotationMetadataExtractor"); - } - - @Override - protected Collection findAttributes(Class clazz) { - return processAnnotation(AnnotationUtils.findAnnotation(clazz, this.annotationType)); - } - - @Override - protected Collection findAttributes(Method method, Class targetClass) { - return processAnnotation(AnnotationUtils.findAnnotation(method, this.annotationType)); - } - - @Override - public @Nullable Collection getAllConfigAttributes() { - return null; - } - - private @Nullable Collection processAnnotation(@Nullable Annotation annotation) { - return (annotation != null) ? this.annotationExtractor.extractAttributes(annotation) : null; - } - - static class SecuredAnnotationMetadataExtractor implements AnnotationMetadataExtractor { - - @Override - public Collection extractAttributes(Secured secured) { - String[] attributeTokens = secured.value(); - List attributes = new ArrayList<>(attributeTokens.length); - for (String token : attributeTokens) { - attributes.add(new SecurityConfig(token)); - } - return attributes; - } - - } - -} diff --git a/access/src/main/java/org/springframework/security/access/event/AbstractAuthorizationEvent.java b/access/src/main/java/org/springframework/security/access/event/AbstractAuthorizationEvent.java deleted file mode 100644 index d71c4339a98..00000000000 --- a/access/src/main/java/org/springframework/security/access/event/AbstractAuthorizationEvent.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.event; - -import org.springframework.context.ApplicationEvent; - -/** - * Abstract superclass for all security interception related events. - * - * @author Ben Alex - * @deprecated Authorization events have moved. Consider - * {@link org.springframework.security.authorization.event.AuthorizationGrantedEvent} and - * {@link org.springframework.security.authorization.event.AuthorizationDeniedEvent} - */ -@Deprecated -public abstract class AbstractAuthorizationEvent extends ApplicationEvent { - - /** - * Construct the event, passing in the secure object being intercepted. - * @param secureObject the secure object - */ - public AbstractAuthorizationEvent(Object secureObject) { - super(secureObject); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/event/AuthenticationCredentialsNotFoundEvent.java b/access/src/main/java/org/springframework/security/access/event/AuthenticationCredentialsNotFoundEvent.java deleted file mode 100644 index 8d7107ed5bb..00000000000 --- a/access/src/main/java/org/springframework/security/access/event/AuthenticationCredentialsNotFoundEvent.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.event; - -import java.util.Collection; - -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.util.Assert; - -/** - * Indicates a secure object invocation failed because the Authentication - * could not be obtained from the SecurityContextHolder. - * - * @author Ben Alex - * @deprecated Authentication is now separated from authorization. Consider - * {@link org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent} - * instead. - */ -@Deprecated -@SuppressWarnings("serial") -public class AuthenticationCredentialsNotFoundEvent extends AbstractAuthorizationEvent { - - private final AuthenticationCredentialsNotFoundException credentialsNotFoundException; - - private final Collection configAttribs; - - /** - * Construct the event. - * @param secureObject the secure object - * @param attributes that apply to the secure object - * @param credentialsNotFoundException exception returned to the caller (contains - * reason) - * - */ - public AuthenticationCredentialsNotFoundEvent(Object secureObject, Collection attributes, - AuthenticationCredentialsNotFoundException credentialsNotFoundException) { - super(secureObject); - Assert.isTrue(attributes != null && credentialsNotFoundException != null, - "All parameters are required and cannot be null"); - this.configAttribs = attributes; - this.credentialsNotFoundException = credentialsNotFoundException; - } - - public Collection getConfigAttributes() { - return this.configAttribs; - } - - public AuthenticationCredentialsNotFoundException getCredentialsNotFoundException() { - return this.credentialsNotFoundException; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/event/AuthorizationFailureEvent.java b/access/src/main/java/org/springframework/security/access/event/AuthorizationFailureEvent.java deleted file mode 100644 index fba28adf0bb..00000000000 --- a/access/src/main/java/org/springframework/security/access/event/AuthorizationFailureEvent.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.event; - -import java.util.Collection; - -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * Indicates a secure object invocation failed because the principal could not be - * authorized for the request. - * - *

- * This event might be thrown as a result of either an - * {@link org.springframework.security.access.AccessDecisionManager AccessDecisionManager} - * or an {@link org.springframework.security.access.intercept.AfterInvocationManager - * AfterInvocationManager}. - * - * @author Ben Alex - * @deprecated Use - * {@link org.springframework.security.authorization.event.AuthorizationDeniedEvent} - * instead - */ -@Deprecated -@SuppressWarnings("serial") -public class AuthorizationFailureEvent extends AbstractAuthorizationEvent { - - private final AccessDeniedException accessDeniedException; - - private final Authentication authentication; - - private final Collection configAttributes; - - /** - * Construct the event. - * @param secureObject the secure object - * @param attributes that apply to the secure object - * @param authentication that was found in the SecurityContextHolder - * @param accessDeniedException that was returned by the - * AccessDecisionManager - * @throws IllegalArgumentException if any null arguments are presented. - */ - public AuthorizationFailureEvent(Object secureObject, Collection attributes, - Authentication authentication, AccessDeniedException accessDeniedException) { - super(secureObject); - Assert.isTrue(attributes != null && authentication != null && accessDeniedException != null, - "All parameters are required and cannot be null"); - this.configAttributes = attributes; - this.authentication = authentication; - this.accessDeniedException = accessDeniedException; - } - - public AccessDeniedException getAccessDeniedException() { - return this.accessDeniedException; - } - - public Authentication getAuthentication() { - return this.authentication; - } - - public Collection getConfigAttributes() { - return this.configAttributes; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/event/AuthorizedEvent.java b/access/src/main/java/org/springframework/security/access/event/AuthorizedEvent.java deleted file mode 100644 index 3ec29ce6a2c..00000000000 --- a/access/src/main/java/org/springframework/security/access/event/AuthorizedEvent.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.event; - -import java.util.Collection; - -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * Event indicating a secure object was invoked successfully. - *

- * Published just before the secure object attempts to proceed. - *

- * - * @author Ben Alex - * @deprecated Use - * {@link org.springframework.security.authorization.event.AuthorizationGrantedEvent} - * instead - */ -@Deprecated -@SuppressWarnings("serial") -public class AuthorizedEvent extends AbstractAuthorizationEvent { - - private final Authentication authentication; - - private final Collection configAttributes; - - /** - * Construct the event. - * @param secureObject the secure object - * @param attributes that apply to the secure object - * @param authentication that successfully called the secure object - * - */ - public AuthorizedEvent(Object secureObject, Collection attributes, Authentication authentication) { - super(secureObject); - Assert.isTrue(attributes != null && authentication != null, "All parameters are required and cannot be null"); - this.configAttributes = attributes; - this.authentication = authentication; - } - - public Authentication getAuthentication() { - return this.authentication; - } - - public Collection getConfigAttributes() { - return this.configAttributes; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/event/LoggerListener.java b/access/src/main/java/org/springframework/security/access/event/LoggerListener.java deleted file mode 100644 index 3c091f15185..00000000000 --- a/access/src/main/java/org/springframework/security/access/event/LoggerListener.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.event; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.context.ApplicationListener; -import org.springframework.core.log.LogMessage; - -/** - * Outputs interceptor-related application events to Commons Logging. - *

- * All failures are logged at the warning level, with success events logged at the - * information level, and public invocation events logged at the debug level. - *

- * - * @author Ben Alex - * @deprecated Logging is now embedded in Spring Security components. If you need further - * logging, please consider using your own {@link ApplicationListener} - */ -@Deprecated -public class LoggerListener implements ApplicationListener { - - private static final Log logger = LogFactory.getLog(LoggerListener.class); - - @Override - public void onApplicationEvent(AbstractAuthorizationEvent event) { - if (event instanceof AuthenticationCredentialsNotFoundEvent) { - onAuthenticationCredentialsNotFoundEvent((AuthenticationCredentialsNotFoundEvent) event); - } - if (event instanceof AuthorizationFailureEvent) { - onAuthorizationFailureEvent((AuthorizationFailureEvent) event); - } - if (event instanceof AuthorizedEvent) { - onAuthorizedEvent((AuthorizedEvent) event); - } - if (event instanceof PublicInvocationEvent) { - onPublicInvocationEvent((PublicInvocationEvent) event); - } - } - - private void onAuthenticationCredentialsNotFoundEvent(AuthenticationCredentialsNotFoundEvent authEvent) { - logger.warn(LogMessage.format( - "Security interception failed due to: %s; secure object: %s; configuration attributes: %s", - authEvent.getCredentialsNotFoundException(), authEvent.getSource(), authEvent.getConfigAttributes())); - } - - private void onPublicInvocationEvent(PublicInvocationEvent event) { - logger.info(LogMessage.format("Security interception not required for public secure object: %s", - event.getSource())); - } - - private void onAuthorizedEvent(AuthorizedEvent authEvent) { - logger.info(LogMessage.format( - "Security authorized for authenticated principal: %s; secure object: %s; configuration attributes: %s", - authEvent.getAuthentication(), authEvent.getSource(), authEvent.getConfigAttributes())); - } - - private void onAuthorizationFailureEvent(AuthorizationFailureEvent authEvent) { - logger.warn(LogMessage.format( - "Security authorization failed due to: %s; authenticated principal: %s; secure object: %s; configuration attributes: %s", - authEvent.getAccessDeniedException(), authEvent.getAuthentication(), authEvent.getSource(), - authEvent.getConfigAttributes())); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/event/PublicInvocationEvent.java b/access/src/main/java/org/springframework/security/access/event/PublicInvocationEvent.java deleted file mode 100644 index 7289d8a1edd..00000000000 --- a/access/src/main/java/org/springframework/security/access/event/PublicInvocationEvent.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.event; - -import org.springframework.security.authorization.event.AuthorizationGrantedEvent; - -/** - * Event that is generated whenever a public secure object is invoked. - *

- * A public secure object is a secure object that has no ConfigAttributes - * defined. A public secure object will not cause the SecurityContextHolder - * to be inspected or authenticated, and no authorization will take place. - *

- *

- * Published just before the secure object attempts to proceed. - *

- * - * @author Ben Alex - * @deprecated Only used by now-deprecated classes. Consider - * {@link AuthorizationGrantedEvent#getSource()} to deduce public invocations. - */ -@Deprecated -@SuppressWarnings("serial") -public class PublicInvocationEvent extends AbstractAuthorizationEvent { - - /** - * Construct the event, passing in the public secure object. - * @param secureObject the public secure object - */ - public PublicInvocationEvent(Object secureObject) { - super(secureObject); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/event/package-info.java b/access/src/main/java/org/springframework/security/access/event/package-info.java deleted file mode 100644 index a381c249e1d..00000000000 --- a/access/src/main/java/org/springframework/security/access/event/package-info.java +++ /dev/null @@ -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. - */ - -/** - * Authorization event and listener classes. - */ -@NullMarked -package org.springframework.security.access.event; - -import org.jspecify.annotations.NullMarked; diff --git a/access/src/main/java/org/springframework/security/access/expression/method/AbstractExpressionBasedMethodConfigAttribute.java b/access/src/main/java/org/springframework/security/access/expression/method/AbstractExpressionBasedMethodConfigAttribute.java deleted file mode 100644 index e3a6d176f1b..00000000000 --- a/access/src/main/java/org/springframework/security/access/expression/method/AbstractExpressionBasedMethodConfigAttribute.java +++ /dev/null @@ -1,82 +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.access.expression.method; - -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.Expression; -import org.springframework.expression.ParseException; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.util.Assert; - -/** - * Contains both filtering and authorization expression meta-data for Spring-EL based - * access control. - *

- * Base class for pre or post-invocation phases of a method invocation. - *

- * Either filter or authorization expressions may be null, but not both. - * - * @author Luke Taylor - * @since 3.0 - * @deprecated Use {@link org.springframework.security.authorization.AuthorizationManager} - * interceptors instead - */ -@NullUnmarked -@Deprecated -abstract class AbstractExpressionBasedMethodConfigAttribute implements ConfigAttribute { - - private final @Nullable Expression filterExpression; - - private final @Nullable Expression authorizeExpression; - - /** - * Parses the supplied expressions as Spring-EL. - */ - AbstractExpressionBasedMethodConfigAttribute(String filterExpression, String authorizeExpression) - throws ParseException { - Assert.isTrue(filterExpression != null || authorizeExpression != null, - "Filter and authorization Expressions cannot both be null"); - SpelExpressionParser parser = new SpelExpressionParser(); - this.filterExpression = (filterExpression != null) ? parser.parseExpression(filterExpression) : null; - this.authorizeExpression = (authorizeExpression != null) ? parser.parseExpression(authorizeExpression) : null; - } - - AbstractExpressionBasedMethodConfigAttribute(Expression filterExpression, Expression authorizeExpression) - throws ParseException { - Assert.isTrue(filterExpression != null || authorizeExpression != null, - "Filter and authorization Expressions cannot both be null"); - this.filterExpression = (filterExpression != null) ? filterExpression : null; - this.authorizeExpression = (authorizeExpression != null) ? authorizeExpression : null; - } - - Expression getFilterExpression() { - return this.filterExpression; - } - - Expression getAuthorizeExpression() { - return this.authorizeExpression; - } - - @Override - public @Nullable String getAttribute() { - return null; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/expression/method/ExpressionBasedAnnotationAttributeFactory.java b/access/src/main/java/org/springframework/security/access/expression/method/ExpressionBasedAnnotationAttributeFactory.java deleted file mode 100644 index e0f9266c7cc..00000000000 --- a/access/src/main/java/org/springframework/security/access/expression/method/ExpressionBasedAnnotationAttributeFactory.java +++ /dev/null @@ -1,107 +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.access.expression.method; - -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.Expression; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.ParseException; -import org.springframework.security.access.prepost.PostInvocationAttribute; -import org.springframework.security.access.prepost.PreInvocationAttribute; -import org.springframework.security.access.prepost.PrePostInvocationAttributeFactory; -import org.springframework.util.Assert; - -/** - * {@link PrePostInvocationAttributeFactory} which interprets the annotation value as an - * expression to be evaluated at runtime. - * - * @author Luke Taylor - * @author Rob Winch - * @since 3.0 - * @deprecated Use {@link org.springframework.security.authorization.AuthorizationManager} - * interceptors instead - */ -@NullUnmarked -@Deprecated -public class ExpressionBasedAnnotationAttributeFactory implements PrePostInvocationAttributeFactory { - - private final Object parserLock = new Object(); - - private @Nullable ExpressionParser parser; - - private MethodSecurityExpressionHandler handler; - - public ExpressionBasedAnnotationAttributeFactory(MethodSecurityExpressionHandler handler) { - Assert.notNull(handler, "handler cannot be null"); - this.handler = handler; - } - - @Override - public PreInvocationAttribute createPreInvocationAttribute(String preFilterAttribute, String filterObject, - String preAuthorizeAttribute) { - try { - // TODO: Optimization of permitAll - ExpressionParser parser = getParser(); - Expression preAuthorizeExpression = (preAuthorizeAttribute != null) - ? parser.parseExpression(preAuthorizeAttribute) : parser.parseExpression("permitAll"); - Expression preFilterExpression = (preFilterAttribute != null) ? parser.parseExpression(preFilterAttribute) - : null; - return new PreInvocationExpressionAttribute(preFilterExpression, filterObject, preAuthorizeExpression); - } - catch (ParseException ex) { - throw new IllegalArgumentException("Failed to parse expression '" + ex.getExpressionString() + "'", ex); - } - } - - @Override - public @Nullable PostInvocationAttribute createPostInvocationAttribute(String postFilterAttribute, - String postAuthorizeAttribute) { - try { - ExpressionParser parser = getParser(); - Expression postAuthorizeExpression = (postAuthorizeAttribute != null) - ? parser.parseExpression(postAuthorizeAttribute) : null; - Expression postFilterExpression = (postFilterAttribute != null) - ? parser.parseExpression(postFilterAttribute) : null; - if (postFilterExpression != null || postAuthorizeExpression != null) { - return new PostInvocationExpressionAttribute(postFilterExpression, postAuthorizeExpression); - } - } - catch (ParseException ex) { - throw new IllegalArgumentException("Failed to parse expression '" + ex.getExpressionString() + "'", ex); - } - - return null; - } - - /** - * Delay the lookup of the {@link ExpressionParser} to prevent SEC-2136 - * @return - */ - private ExpressionParser getParser() { - if (this.parser != null) { - return this.parser; - } - synchronized (this.parserLock) { - this.parser = this.handler.getExpressionParser(); - this.handler = null; - } - return this.parser; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/expression/method/ExpressionBasedPostInvocationAdvice.java b/access/src/main/java/org/springframework/security/access/expression/method/ExpressionBasedPostInvocationAdvice.java deleted file mode 100644 index ad1465bf410..00000000000 --- a/access/src/main/java/org/springframework/security/access/expression/method/ExpressionBasedPostInvocationAdvice.java +++ /dev/null @@ -1,74 +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.access.expression.method; - -import org.aopalliance.intercept.MethodInvocation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.expression.ExpressionUtils; -import org.springframework.security.access.prepost.PostInvocationAttribute; -import org.springframework.security.access.prepost.PostInvocationAuthorizationAdvice; -import org.springframework.security.core.Authentication; - -/** - * @author Luke Taylor - * @since 3.0 - * @deprecated Use - * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor} - * instead - */ -@Deprecated -public class ExpressionBasedPostInvocationAdvice implements PostInvocationAuthorizationAdvice { - - protected final Log logger = LogFactory.getLog(getClass()); - - private final MethodSecurityExpressionHandler expressionHandler; - - public ExpressionBasedPostInvocationAdvice(MethodSecurityExpressionHandler expressionHandler) { - this.expressionHandler = expressionHandler; - } - - @Override - public Object after(Authentication authentication, MethodInvocation mi, PostInvocationAttribute postAttr, - Object returnedObject) throws AccessDeniedException { - PostInvocationExpressionAttribute pia = (PostInvocationExpressionAttribute) postAttr; - EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, mi); - Expression postFilter = pia.getFilterExpression(); - Expression postAuthorize = pia.getAuthorizeExpression(); - if (postFilter != null) { - this.logger.debug(LogMessage.format("Applying PostFilter expression %s", postFilter)); - if (returnedObject != null) { - returnedObject = this.expressionHandler.filter(returnedObject, postFilter, ctx); - } - else { - this.logger.debug("Return object is null, filtering will be skipped"); - } - } - this.expressionHandler.setReturnObject(returnedObject, ctx); - if (postAuthorize != null && !ExpressionUtils.evaluateAsBoolean(postAuthorize, ctx)) { - this.logger.debug("PostAuthorize expression rejected access"); - throw new AccessDeniedException("Access is denied"); - } - return returnedObject; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/expression/method/ExpressionBasedPreInvocationAdvice.java b/access/src/main/java/org/springframework/security/access/expression/method/ExpressionBasedPreInvocationAdvice.java deleted file mode 100644 index 2ff4cdf5e89..00000000000 --- a/access/src/main/java/org/springframework/security/access/expression/method/ExpressionBasedPreInvocationAdvice.java +++ /dev/null @@ -1,88 +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.access.expression.method; - -import java.util.Collection; - -import org.aopalliance.intercept.MethodInvocation; -import org.jspecify.annotations.NullUnmarked; - -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.security.access.expression.ExpressionUtils; -import org.springframework.security.access.prepost.PreInvocationAttribute; -import org.springframework.security.access.prepost.PreInvocationAuthorizationAdvice; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * Method pre-invocation handling based on expressions. - * - * @author Luke Taylor - * @since 3.0 - * @deprecated Use - * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor} - * instead - */ -@NullUnmarked -@Deprecated -public class ExpressionBasedPreInvocationAdvice implements PreInvocationAuthorizationAdvice { - - private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); - - @Override - public boolean before(Authentication authentication, MethodInvocation mi, PreInvocationAttribute attr) { - PreInvocationExpressionAttribute preAttr = (PreInvocationExpressionAttribute) attr; - EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, mi); - Expression preFilter = preAttr.getFilterExpression(); - Expression preAuthorize = preAttr.getAuthorizeExpression(); - if (preFilter != null) { - Object filterTarget = findFilterTarget(preAttr.getFilterTarget(), ctx, mi); - this.expressionHandler.filter(filterTarget, preFilter, ctx); - } - return (preAuthorize != null) ? ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx) : true; - } - - private Object findFilterTarget(String filterTargetName, EvaluationContext ctx, MethodInvocation invocation) { - Object filterTarget = null; - if (filterTargetName.length() > 0) { - filterTarget = ctx.lookupVariable(filterTargetName); - Assert.notNull(filterTarget, - () -> "Filter target was null, or no argument with name " + filterTargetName + " found in method"); - } - else if (invocation.getArguments().length == 1) { - Object arg = invocation.getArguments()[0]; - if (arg.getClass().isArray() || arg instanceof Collection) { - filterTarget = arg; - } - Assert.notNull(filterTarget, () -> "A PreFilter expression was set but the method argument type" - + arg.getClass() + " is not filterable"); - } - else if (invocation.getArguments().length > 1) { - throw new IllegalArgumentException( - "Unable to determine the method argument for filtering. Specify the filter target."); - } - Assert.isTrue(!filterTarget.getClass().isArray(), - "Pre-filtering on array types is not supported. Using a Collection will solve this problem"); - return filterTarget; - } - - public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.expressionHandler = expressionHandler; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/expression/method/PostInvocationExpressionAttribute.java b/access/src/main/java/org/springframework/security/access/expression/method/PostInvocationExpressionAttribute.java deleted file mode 100644 index 7cd59af1098..00000000000 --- a/access/src/main/java/org/springframework/security/access/expression/method/PostInvocationExpressionAttribute.java +++ /dev/null @@ -1,56 +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.access.expression.method; - -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.Expression; -import org.springframework.expression.ParseException; -import org.springframework.security.access.prepost.PostInvocationAttribute; - -/** - * @author Luke Taylor - * @since 3.0 - * @deprecated Use - * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor} - * instead - */ -@Deprecated -@SuppressWarnings("serial") -class PostInvocationExpressionAttribute extends AbstractExpressionBasedMethodConfigAttribute - implements PostInvocationAttribute { - - PostInvocationExpressionAttribute(String filterExpression, String authorizeExpression) throws ParseException { - super(filterExpression, authorizeExpression); - } - - PostInvocationExpressionAttribute(@Nullable Expression filterExpression, @Nullable Expression authorizeExpression) - throws ParseException { - super(filterExpression, authorizeExpression); - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - Expression authorize = getAuthorizeExpression(); - Expression filter = getFilterExpression(); - sb.append("[authorize: '").append((authorize != null) ? authorize.getExpressionString() : "null"); - sb.append("', filter: '").append((filter != null) ? filter.getExpressionString() : "null").append("']"); - return sb.toString(); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/expression/method/PreInvocationExpressionAttribute.java b/access/src/main/java/org/springframework/security/access/expression/method/PreInvocationExpressionAttribute.java deleted file mode 100644 index 8379087d043..00000000000 --- a/access/src/main/java/org/springframework/security/access/expression/method/PreInvocationExpressionAttribute.java +++ /dev/null @@ -1,71 +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.access.expression.method; - -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.Expression; -import org.springframework.expression.ParseException; -import org.springframework.security.access.prepost.PreInvocationAttribute; - -/** - * @author Luke Taylor - * @since 3.0 - * @deprecated Use - * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor} - * instead - */ -@Deprecated -@SuppressWarnings("serial") -class PreInvocationExpressionAttribute extends AbstractExpressionBasedMethodConfigAttribute - implements PreInvocationAttribute { - - private final String filterTarget; - - PreInvocationExpressionAttribute(String filterExpression, String filterTarget, String authorizeExpression) - throws ParseException { - super(filterExpression, authorizeExpression); - this.filterTarget = filterTarget; - } - - PreInvocationExpressionAttribute(@Nullable Expression filterExpression, String filterTarget, - Expression authorizeExpression) throws ParseException { - super(filterExpression, authorizeExpression); - this.filterTarget = filterTarget; - } - - /** - * The parameter name of the target argument (must be a Collection) to which filtering - * will be applied. - * @return the method parameter name - */ - String getFilterTarget() { - return this.filterTarget; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - Expression authorize = getAuthorizeExpression(); - Expression filter = getFilterExpression(); - sb.append("[authorize: '").append((authorize != null) ? authorize.getExpressionString() : "null"); - sb.append("', filter: '").append((filter != null) ? filter.getExpressionString() : "null"); - sb.append("', filterTarget: '").append(this.filterTarget).append("']"); - return sb.toString(); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/intercept/AbstractSecurityInterceptor.java b/access/src/main/java/org/springframework/security/access/intercept/AbstractSecurityInterceptor.java deleted file mode 100644 index 4043af946a8..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/AbstractSecurityInterceptor.java +++ /dev/null @@ -1,496 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.ApplicationEvent; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.ApplicationEventPublisherAware; -import org.springframework.context.MessageSource; -import org.springframework.context.MessageSourceAware; -import org.springframework.context.support.MessageSourceAccessor; -import org.springframework.core.log.LogMessage; -import org.springframework.security.access.AccessDecisionManager; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityMetadataSource; -import org.springframework.security.access.event.AuthenticationCredentialsNotFoundEvent; -import org.springframework.security.access.event.AuthorizationFailureEvent; -import org.springframework.security.access.event.AuthorizedEvent; -import org.springframework.security.access.event.PublicInvocationEvent; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.SpringSecurityMessageSource; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; - -/** - * Abstract class that implements security interception for secure objects. - *

- * The AbstractSecurityInterceptor will ensure the proper startup - * configuration of the security interceptor. It will also implement the proper handling - * of secure object invocations, namely: - *

    - *
  1. Obtain the {@link Authentication} object from the - * {@link SecurityContextHolder}.
  2. - *
  3. Determine if the request relates to a secured or public invocation by looking up - * the secure object request against the {@link SecurityMetadataSource}.
  4. - *
  5. For an invocation that is secured (there is a list of ConfigAttributes - * for the secure object invocation): - *
      - *
    1. If either the - * {@link org.springframework.security.core.Authentication#isAuthenticated()} returns - * false, or the {@link #alwaysReauthenticate} is true, - * authenticate the request against the configured {@link AuthenticationManager}. When - * authenticated, replace the Authentication object on the - * SecurityContextHolder with the returned value.
    2. - *
    3. Authorize the request against the configured {@link AccessDecisionManager}.
    4. - *
    5. Perform any run-as replacement via the configured {@link RunAsManager}.
    6. - *
    7. Pass control back to the concrete subclass, which will actually proceed with - * executing the object. A {@link InterceptorStatusToken} is returned so that after the - * subclass has finished proceeding with execution of the object, its finally clause can - * ensure the AbstractSecurityInterceptor is re-called and tidies up - * correctly using {@link #finallyInvocation(InterceptorStatusToken)}.
    8. - *
    9. The concrete subclass will re-call the AbstractSecurityInterceptor via - * the {@link #afterInvocation(InterceptorStatusToken, Object)} method.
    10. - *
    11. If the RunAsManager replaced the Authentication object, - * return the SecurityContextHolder to the object that existed after the call - * to AuthenticationManager.
    12. - *
    13. If an AfterInvocationManager is defined, invoke the invocation manager - * and allow it to replace the object due to be returned to the caller.
    14. - *
    - *
  6. - *
  7. For an invocation that is public (there are no ConfigAttributes for - * the secure object invocation): - *
      - *
    1. As described above, the concrete subclass will be returned an - * InterceptorStatusToken which is subsequently re-presented to the - * AbstractSecurityInterceptor after the secure object has been executed. The - * AbstractSecurityInterceptor will take no further action when its - * {@link #afterInvocation(InterceptorStatusToken, Object)} is called.
    2. - *
    - *
  8. - *
  9. Control again returns to the concrete subclass, along with the Object - * that should be returned to the caller. The subclass will then return that result or - * exception to the original caller.
  10. - *
- * - * @author Ben Alex - * @author Rob Winch - * @deprecated Use - * {@link org.springframework.security.web.access.intercept.AuthorizationFilter} instead - * for filter security, - * {@link org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor} - * for messaging security, or - * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor} - * and - * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor} - * for method security. - */ -@NullUnmarked -@Deprecated -public abstract class AbstractSecurityInterceptor - implements InitializingBean, ApplicationEventPublisherAware, MessageSourceAware { - - protected final Log logger = LogFactory.getLog(getClass()); - - protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); - - private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder - .getContextHolderStrategy(); - - private @Nullable ApplicationEventPublisher eventPublisher; - - private @Nullable AccessDecisionManager accessDecisionManager; - - private @Nullable AfterInvocationManager afterInvocationManager; - - private AuthenticationManager authenticationManager = new NoOpAuthenticationManager(); - - private RunAsManager runAsManager = new NullRunAsManager(); - - private boolean alwaysReauthenticate = false; - - private boolean rejectPublicInvocations = false; - - private boolean validateConfigAttributes = true; - - private boolean publishAuthorizationSuccess = false; - - @Override - public void afterPropertiesSet() { - Assert.notNull(getSecureObjectClass(), "Subclass must provide a non-null response to getSecureObjectClass()"); - Assert.notNull(this.messages, "A message source must be set"); - Assert.notNull(this.authenticationManager, "An AuthenticationManager is required"); - Assert.notNull(this.accessDecisionManager, "An AccessDecisionManager is required"); - Assert.notNull(this.runAsManager, "A RunAsManager is required"); - Assert.notNull(this.obtainSecurityMetadataSource(), "An SecurityMetadataSource is required"); - Assert.isTrue(this.obtainSecurityMetadataSource().supports(getSecureObjectClass()), - () -> "SecurityMetadataSource does not support secure object class: " + getSecureObjectClass()); - Assert.isTrue(this.runAsManager.supports(getSecureObjectClass()), - () -> "RunAsManager does not support secure object class: " + getSecureObjectClass()); - Assert.isTrue(this.accessDecisionManager.supports(getSecureObjectClass()), - () -> "AccessDecisionManager does not support secure object class: " + getSecureObjectClass()); - if (this.afterInvocationManager != null) { - Assert.isTrue(this.afterInvocationManager.supports(getSecureObjectClass()), - () -> "AfterInvocationManager does not support secure object class: " + getSecureObjectClass()); - } - if (this.validateConfigAttributes) { - Collection attributeDefs = this.obtainSecurityMetadataSource().getAllConfigAttributes(); - if (attributeDefs == null) { - this.logger.warn("Could not validate configuration attributes as the " - + "SecurityMetadataSource did not return any attributes from getAllConfigAttributes()"); - return; - } - validateAttributeDefs(attributeDefs); - } - } - - private void validateAttributeDefs(Collection attributeDefs) { - Set unsupportedAttrs = new HashSet<>(); - for (ConfigAttribute attr : attributeDefs) { - if (!this.runAsManager.supports(attr) && !this.accessDecisionManager.supports(attr) - && ((this.afterInvocationManager == null) || !this.afterInvocationManager.supports(attr))) { - unsupportedAttrs.add(attr); - } - } - if (unsupportedAttrs.size() != 0) { - this.logger - .trace("Did not validate configuration attributes since validateConfigurationAttributes is false"); - throw new IllegalArgumentException("Unsupported configuration attributes: " + unsupportedAttrs); - } - else { - this.logger.trace("Validated configuration attributes"); - } - } - - protected @Nullable InterceptorStatusToken beforeInvocation(Object object) { - Assert.notNull(object, "Object was null"); - if (!getSecureObjectClass().isAssignableFrom(object.getClass())) { - throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() - + " but AbstractSecurityInterceptor only configured to support secure objects of type: " - + getSecureObjectClass()); - } - Collection attributes = this.obtainSecurityMetadataSource().getAttributes(object); - if (CollectionUtils.isEmpty(attributes)) { - Assert.isTrue(!this.rejectPublicInvocations, - () -> "Secure object invocation " + object - + " was denied as public invocations are not allowed via this interceptor. " - + "This indicates a configuration error because the " - + "rejectPublicInvocations property is set to 'true'"); - if (this.logger.isDebugEnabled()) { - this.logger.debug(LogMessage.format("Authorized public object %s", object)); - } - publishEvent(new PublicInvocationEvent(object)); - return null; // no further work post-invocation - } - if (this.securityContextHolderStrategy.getContext().getAuthentication() == null) { - credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", - "An Authentication object was not found in the SecurityContext"), object, attributes); - } - Authentication authenticated = authenticateIfRequired(); - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes)); - } - // Attempt authorization - attemptAuthorization(object, attributes, authenticated); - if (this.logger.isDebugEnabled()) { - this.logger.debug(LogMessage.format("Authorized %s with attributes %s", object, attributes)); - } - if (this.publishAuthorizationSuccess) { - publishEvent(new AuthorizedEvent(object, attributes, authenticated)); - } - - // Attempt to run as a different user - Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes); - if (runAs != null) { - SecurityContext origCtx = this.securityContextHolderStrategy.getContext(); - SecurityContext newCtx = this.securityContextHolderStrategy.createEmptyContext(); - newCtx.setAuthentication(runAs); - this.securityContextHolderStrategy.setContext(newCtx); - - if (this.logger.isDebugEnabled()) { - this.logger.debug(LogMessage.format("Switched to RunAs authentication %s", runAs)); - } - // need to revert to token.Authenticated post-invocation - return new InterceptorStatusToken(origCtx, true, attributes, object); - } - this.logger.trace("Did not switch RunAs authentication since RunAsManager returned null"); - // no further work post-invocation - return new InterceptorStatusToken(this.securityContextHolderStrategy.getContext(), false, attributes, object); - - } - - private void attemptAuthorization(Object object, Collection attributes, - Authentication authenticated) { - try { - this.accessDecisionManager.decide(authenticated, object, attributes); - } - catch (AccessDeniedException ex) { - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Failed to authorize %s with attributes %s using %s", object, - attributes, this.accessDecisionManager)); - } - else if (this.logger.isDebugEnabled()) { - this.logger.debug(LogMessage.format("Failed to authorize %s with attributes %s", object, attributes)); - } - publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, ex)); - throw ex; - } - } - - /** - * Cleans up the work of the AbstractSecurityInterceptor after the secure - * object invocation has been completed. This method should be invoked after the - * secure object invocation and before afterInvocation regardless of the secure object - * invocation returning successfully (i.e. it should be done in a finally block). - * @param token as returned by the {@link #beforeInvocation(Object)} method - */ - protected void finallyInvocation(InterceptorStatusToken token) { - if (token != null && token.isContextHolderRefreshRequired()) { - this.securityContextHolderStrategy.setContext(token.getSecurityContext()); - if (this.logger.isDebugEnabled()) { - this.logger.debug(LogMessage - .of(() -> "Reverted to original authentication " + token.getSecurityContext().getAuthentication())); - } - } - } - - /** - * Completes the work of the AbstractSecurityInterceptor after the secure - * object invocation has been completed. - * @param token as returned by the {@link #beforeInvocation(Object)} method - * @param returnedObject any object returned from the secure object invocation (may be - * null) - * @return the object the secure object invocation should ultimately return to its - * caller (may be null) - */ - protected Object afterInvocation(InterceptorStatusToken token, @Nullable Object returnedObject) { - if (token == null) { - // public object - return returnedObject; - } - finallyInvocation(token); // continue to clean in this method for passivity - if (this.afterInvocationManager != null) { - // Attempt after invocation handling - try { - returnedObject = this.afterInvocationManager.decide(token.getSecurityContext().getAuthentication(), - token.getSecureObject(), token.getAttributes(), returnedObject); - } - catch (AccessDeniedException ex) { - publishEvent(new AuthorizationFailureEvent(token.getSecureObject(), token.getAttributes(), - token.getSecurityContext().getAuthentication(), ex)); - throw ex; - } - } - return returnedObject; - } - - /** - * Checks the current authentication token and passes it to the AuthenticationManager - * if {@link org.springframework.security.core.Authentication#isAuthenticated()} - * returns false or the property alwaysReauthenticate has been set to true. - * @return an authenticated Authentication object. - */ - private Authentication authenticateIfRequired() { - Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); - if (authentication.isAuthenticated() && !this.alwaysReauthenticate) { - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Did not re-authenticate %s before authorizing", authentication)); - } - return authentication; - } - authentication = this.authenticationManager.authenticate(authentication); - // Don't authenticated.setAuthentication(true) because each provider does that - if (this.logger.isDebugEnabled()) { - this.logger.debug(LogMessage.format("Re-authenticated %s before authorizing", authentication)); - } - SecurityContext context = this.securityContextHolderStrategy.createEmptyContext(); - context.setAuthentication(authentication); - this.securityContextHolderStrategy.setContext(context); - return authentication; - } - - /** - * Helper method which generates an exception containing the passed reason, and - * publishes an event to the application context. - *

- * Always throws an exception. - * @param reason to be provided in the exception detail - * @param secureObject that was being called - * @param configAttribs that were defined for the secureObject - */ - private void credentialsNotFound(String reason, Object secureObject, Collection configAttribs) { - AuthenticationCredentialsNotFoundException exception = new AuthenticationCredentialsNotFoundException(reason); - AuthenticationCredentialsNotFoundEvent event = new AuthenticationCredentialsNotFoundEvent(secureObject, - configAttribs, exception); - publishEvent(event); - throw exception; - } - - public AccessDecisionManager getAccessDecisionManager() { - return this.accessDecisionManager; - } - - public AfterInvocationManager getAfterInvocationManager() { - return this.afterInvocationManager; - } - - public AuthenticationManager getAuthenticationManager() { - return this.authenticationManager; - } - - public RunAsManager getRunAsManager() { - return this.runAsManager; - } - - /** - * Indicates the type of secure objects the subclass will be presenting to the - * abstract parent for processing. This is used to ensure collaborators wired to the - * {@code AbstractSecurityInterceptor} all support the indicated secure object class. - * @return the type of secure object the subclass provides services for - */ - public abstract Class getSecureObjectClass(); - - public boolean isAlwaysReauthenticate() { - return this.alwaysReauthenticate; - } - - public boolean isRejectPublicInvocations() { - return this.rejectPublicInvocations; - } - - public boolean isValidateConfigAttributes() { - return this.validateConfigAttributes; - } - - public abstract SecurityMetadataSource obtainSecurityMetadataSource(); - - /** - * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use - * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. - * - * @since 5.8 - */ - public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { - Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); - this.securityContextHolderStrategy = securityContextHolderStrategy; - } - - public void setAccessDecisionManager(AccessDecisionManager accessDecisionManager) { - this.accessDecisionManager = accessDecisionManager; - } - - public void setAfterInvocationManager(AfterInvocationManager afterInvocationManager) { - this.afterInvocationManager = afterInvocationManager; - } - - /** - * Indicates whether the AbstractSecurityInterceptor should ignore the - * {@link Authentication#isAuthenticated()} property. Defaults to false, - * meaning by default the Authentication.isAuthenticated() property is - * trusted and re-authentication will not occur if the principal has already been - * authenticated. - * @param alwaysReauthenticate true to force - * AbstractSecurityInterceptor to disregard the value of - * Authentication.isAuthenticated() and always re-authenticate the - * request (defaults to false). - */ - public void setAlwaysReauthenticate(boolean alwaysReauthenticate) { - this.alwaysReauthenticate = alwaysReauthenticate; - } - - @Override - public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { - this.eventPublisher = applicationEventPublisher; - } - - public void setAuthenticationManager(AuthenticationManager newManager) { - this.authenticationManager = newManager; - } - - @Override - public void setMessageSource(MessageSource messageSource) { - this.messages = new MessageSourceAccessor(messageSource); - } - - /** - * Only {@code AuthorizationFailureEvent} will be published. If you set this property - * to {@code true}, {@code AuthorizedEvent}s will also be published. - * @param publishAuthorizationSuccess default value is {@code false} - */ - public void setPublishAuthorizationSuccess(boolean publishAuthorizationSuccess) { - this.publishAuthorizationSuccess = publishAuthorizationSuccess; - } - - /** - * By rejecting public invocations (and setting this property to true), - * essentially you are ensuring that every secure object invocation advised by - * AbstractSecurityInterceptor has a configuration attribute defined. - * This is useful to ensure a "fail safe" mode where undeclared secure objects will be - * rejected and configuration omissions detected early. An - * IllegalArgumentException will be thrown by the - * AbstractSecurityInterceptor if you set this property to true and - * an attempt is made to invoke a secure object that has no configuration attributes. - * @param rejectPublicInvocations set to true to reject invocations of - * secure objects that have no configuration attributes (by default it is - * false which treats undeclared secure objects as "public" or - * unauthorized). - */ - public void setRejectPublicInvocations(boolean rejectPublicInvocations) { - this.rejectPublicInvocations = rejectPublicInvocations; - } - - public void setRunAsManager(RunAsManager runAsManager) { - this.runAsManager = runAsManager; - } - - public void setValidateConfigAttributes(boolean validateConfigAttributes) { - this.validateConfigAttributes = validateConfigAttributes; - } - - private void publishEvent(ApplicationEvent event) { - if (this.eventPublisher != null) { - this.eventPublisher.publishEvent(event); - } - } - - private static class NoOpAuthenticationManager implements AuthenticationManager { - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - throw new AuthenticationServiceException("Cannot authenticate " + authentication); - } - - } - -} diff --git a/access/src/main/java/org/springframework/security/access/intercept/AfterInvocationManager.java b/access/src/main/java/org/springframework/security/access/intercept/AfterInvocationManager.java deleted file mode 100644 index 39d712f8808..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/AfterInvocationManager.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import java.util.Collection; - -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.Authentication; - -/** - * Reviews the Object returned from a secure object invocation, being able to - * modify the Object or throw an {@link AccessDeniedException}. - *

- * Typically used to ensure the principal is permitted to access the domain object - * instance returned by a service layer bean. Can also be used to mutate the domain object - * instance so the principal is only able to access authorised bean properties or - * Collection elements. - *

- * Special consideration should be given to using an AfterInvocationManager - * on bean methods that modify a database. Typically an - * AfterInvocationManager is used with read-only methods, such as - * public DomainObject getById(id). If used with methods that modify a - * database, a transaction manager should be used to ensure any - * AccessDeniedException will cause a rollback of the changes made by the - * transaction. - *

- * - * @author Ben Alex - * @see org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor - * @see org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor - * @deprecated Use delegation with {@link AuthorizationManager} - */ -@Deprecated -public interface AfterInvocationManager { - - /** - * Given the details of a secure object invocation including its returned - * Object, make an access control decision or optionally modify the - * returned Object. - * @param authentication the caller that invoked the method - * @param object the secured object that was called - * @param attributes the configuration attributes associated with the secured object - * that was invoked - * @param returnedObject the Object that was returned from the secure - * object invocation - * @return the Object that will ultimately be returned to the caller (if - * an implementation does not wish to modify the object to be returned to the caller, - * the implementation should simply return the same object it was passed by the - * returnedObject method argument) - * @throws AccessDeniedException if access is denied - */ - Object decide(Authentication authentication, Object object, Collection attributes, - Object returnedObject) throws AccessDeniedException; - - /** - * Indicates whether this AfterInvocationManager is able to process - * "after invocation" requests presented with the passed ConfigAttribute. - *

- * This allows the AbstractSecurityInterceptor to check every - * configuration attribute can be consumed by the configured - * AccessDecisionManager and/or RunAsManager and/or - * AfterInvocationManager. - *

- * @param attribute a configuration attribute that has been configured against the - * AbstractSecurityInterceptor - * @return true if this AfterInvocationManager can support the passed - * configuration attribute - */ - boolean supports(ConfigAttribute attribute); - - /** - * Indicates whether the AfterInvocationManager implementation is able to - * provide access control decisions for the indicated secured object type. - * @param clazz the class that is being queried - * @return true if the implementation can process the indicated class - */ - boolean supports(Class clazz); - -} diff --git a/access/src/main/java/org/springframework/security/access/intercept/AfterInvocationProviderManager.java b/access/src/main/java/org/springframework/security/access/intercept/AfterInvocationProviderManager.java deleted file mode 100644 index 91f7f1cf162..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/AfterInvocationProviderManager.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.core.log.LogMessage; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.AfterInvocationProvider; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; - -/** - * Provider-based implementation of {@link AfterInvocationManager}. - *

- * Handles configuration of a bean context defined list of {@link AfterInvocationProvider} - * s. - *

- * Every AfterInvocationProvider will be polled when the - * {@link #decide(Authentication, Object, Collection, Object)} method is called. The - * Object returned from each provider will be presented to the successive - * provider for processing. This means each provider must ensure they return the - * Object, even if they are not interested in the "after invocation" decision - * (perhaps as the secure object invocation did not include a configuration attribute a - * given provider is configured to respond to). - * - * @author Ben Alex - * @see org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor - * @see org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor - * @deprecated Use delegation with {@link AuthorizationManager} - */ -@NullUnmarked -@Deprecated -public class AfterInvocationProviderManager implements AfterInvocationManager, InitializingBean { - - protected static final Log logger = LogFactory.getLog(AfterInvocationProviderManager.class); - - @SuppressWarnings("NullAway.Init") - private @Nullable List providers; - - @Override - public void afterPropertiesSet() { - checkIfValidList(this.providers); - } - - @Override - public Object decide(Authentication authentication, Object object, Collection config, - Object returnedObject) throws AccessDeniedException { - Object result = returnedObject; - for (AfterInvocationProvider provider : this.providers) { - result = provider.decide(authentication, object, config, result); - } - return result; - } - - public List getProviders() { - return this.providers; - } - - public void setProviders(List newList) { - checkIfValidList(newList); - this.providers = new ArrayList<>(newList.size()); - for (Object currentObject : newList) { - Assert.isInstanceOf(AfterInvocationProvider.class, currentObject, () -> "AfterInvocationProvider " - + currentObject.getClass().getName() + " must implement AfterInvocationProvider"); - this.providers.add((AfterInvocationProvider) currentObject); - } - } - - private void checkIfValidList(List listToCheck) { - Assert.isTrue(!CollectionUtils.isEmpty(listToCheck), "A list of AfterInvocationProviders is required"); - } - - @Override - public boolean supports(ConfigAttribute attribute) { - for (AfterInvocationProvider provider : this.providers) { - logger.debug(LogMessage.format("Evaluating %s against %s", attribute, provider)); - if (provider.supports(attribute)) { - return true; - } - } - return false; - } - - /** - * Iterates through all AfterInvocationProviders and ensures each can - * support the presented class. - *

- * If one or more providers cannot support the presented class, false is - * returned. - * @param clazz the secure object class being queries - * @return if the AfterInvocationProviderManager can support the secure - * object class, which requires every one of its AfterInvocationProviders - * to support the secure object class - */ - @Override - public boolean supports(Class clazz) { - for (AfterInvocationProvider provider : this.providers) { - if (!provider.supports(clazz)) { - return false; - } - } - return true; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/intercept/InterceptorStatusToken.java b/access/src/main/java/org/springframework/security/access/intercept/InterceptorStatusToken.java deleted file mode 100644 index 56abcc0d57e..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/InterceptorStatusToken.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import java.util.Collection; - -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.context.SecurityContext; - -/** - * A return object received by {@link AbstractSecurityInterceptor} subclasses. - *

- * This class reflects the status of the security interception, so that the final call to - * {@link org.springframework.security.access.intercept.AbstractSecurityInterceptor#afterInvocation(InterceptorStatusToken, Object)} - * can tidy up correctly. - * - * @author Ben Alex - * @see org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor - * @see org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor - * @deprecated Use delegation with {@link AuthorizationManager} - */ -@Deprecated -public class InterceptorStatusToken { - - private SecurityContext securityContext; - - private Collection attr; - - private Object secureObject; - - private boolean contextHolderRefreshRequired; - - public InterceptorStatusToken(SecurityContext securityContext, boolean contextHolderRefreshRequired, - Collection attributes, Object secureObject) { - this.securityContext = securityContext; - this.contextHolderRefreshRequired = contextHolderRefreshRequired; - this.attr = attributes; - this.secureObject = secureObject; - } - - public Collection getAttributes() { - return this.attr; - } - - public SecurityContext getSecurityContext() { - return this.securityContext; - } - - public Object getSecureObject() { - return this.secureObject; - } - - public boolean isContextHolderRefreshRequired() { - return this.contextHolderRefreshRequired; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/intercept/MethodInvocationPrivilegeEvaluator.java b/access/src/main/java/org/springframework/security/access/intercept/MethodInvocationPrivilegeEvaluator.java deleted file mode 100644 index 1041c5080bc..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/MethodInvocationPrivilegeEvaluator.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import java.util.Collection; - -import org.aopalliance.intercept.MethodInvocation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.core.log.LogMessage; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * Allows users to determine whether they have "before invocation" privileges for a given - * method invocation. - *

- * Of course, if an - * {@link org.springframework.security.access.intercept.AfterInvocationManager} is used to - * authorize the result of a method invocation, this class cannot assist - * determine whether or not the AfterInvocationManager will enable access. - * Instead this class aims to allow applications to determine whether or not the current - * principal would be allowed to at least attempt to invoke the method, irrespective of - * the "after" invocation handling. - *

- * - * @author Ben Alex - * @deprecated Use {@link org.springframework.security.authorization.AuthorizationManager} - * instead - */ -@NullUnmarked -@Deprecated -public class MethodInvocationPrivilegeEvaluator implements InitializingBean { - - protected static final Log logger = LogFactory.getLog(MethodInvocationPrivilegeEvaluator.class); - - @SuppressWarnings("NullAway.Init") - private @Nullable AbstractSecurityInterceptor securityInterceptor; - - @Override - public void afterPropertiesSet() { - Assert.notNull(this.securityInterceptor, "SecurityInterceptor required"); - } - - public boolean isAllowed(MethodInvocation invocation, Authentication authentication) { - Assert.notNull(invocation, "MethodInvocation required"); - Assert.notNull(invocation.getMethod(), "MethodInvocation must provide a non-null getMethod()"); - Collection attrs = this.securityInterceptor.obtainSecurityMetadataSource() - .getAttributes(invocation); - if (attrs == null) { - return !this.securityInterceptor.isRejectPublicInvocations(); - } - if (authentication == null || authentication.getAuthorities().isEmpty()) { - return false; - } - try { - this.securityInterceptor.getAccessDecisionManager().decide(authentication, invocation, attrs); - return true; - } - catch (AccessDeniedException unauthorized) { - logger.debug(LogMessage.format("%s denied for %s", invocation, authentication), unauthorized); - return false; - } - } - - public void setSecurityInterceptor(AbstractSecurityInterceptor securityInterceptor) { - Assert.notNull(securityInterceptor, "AbstractSecurityInterceptor cannot be null"); - Assert.isTrue(MethodInvocation.class.equals(securityInterceptor.getSecureObjectClass()), - "AbstractSecurityInterceptor does not support MethodInvocations"); - Assert.notNull(securityInterceptor.getAccessDecisionManager(), - "AbstractSecurityInterceptor must provide a non-null AccessDecisionManager"); - this.securityInterceptor = securityInterceptor; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/intercept/NullRunAsManager.java b/access/src/main/java/org/springframework/security/access/intercept/NullRunAsManager.java deleted file mode 100644 index 819e3fb576e..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/NullRunAsManager.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import java.util.Collection; - -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.core.Authentication; - -/** - * Implementation of a {@link RunAsManager} that does nothing. - *

- * This class should be used if you do not require run-as authentication replacement - * functionality. - * - * @author Ben Alex - * @deprecated please see {@link RunAsManager} deprecation notice - */ -@NullUnmarked -@Deprecated -final class NullRunAsManager implements RunAsManager { - - @Override - public @Nullable Authentication buildRunAs(Authentication authentication, Object object, - Collection config) { - return null; - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return false; - } - - @Override - public boolean supports(Class clazz) { - return true; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/intercept/RunAsImplAuthenticationProvider.java b/access/src/main/java/org/springframework/security/access/intercept/RunAsImplAuthenticationProvider.java deleted file mode 100644 index 9dc1feacacd..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/RunAsImplAuthenticationProvider.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.MessageSource; -import org.springframework.context.MessageSourceAware; -import org.springframework.context.support.MessageSourceAccessor; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.SpringSecurityMessageSource; -import org.springframework.util.Assert; - -/** - * An {@link AuthenticationProvider} implementation that can authenticate a - * {@link RunAsUserToken}. - *

- * Configured in the bean context with a key that should match the key used by adapters to - * generate the RunAsUserToken. It treats as valid any - * RunAsUserToken instance presenting a hash code that matches the - * RunAsImplAuthenticationProvider-configured key. - *

- *

- * If the key does not match, a BadCredentialsException is thrown. - *

- * - * @deprecated Authentication is now separated from authorization in Spring Security. This - * class is only used by now-deprecated components. There is not yet an equivalent - * replacement in Spring Security. - */ -@NullUnmarked -@Deprecated -public class RunAsImplAuthenticationProvider implements InitializingBean, AuthenticationProvider, MessageSourceAware { - - protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); - - @SuppressWarnings("NullAway.Init") - private @Nullable String key; - - @Override - public void afterPropertiesSet() { - Assert.notNull(this.key, "A Key is required and should match that configured for the RunAsManagerImpl"); - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - RunAsUserToken token = (RunAsUserToken) authentication; - if (token.getKeyHash() != this.key.hashCode()) { - throw new BadCredentialsException(this.messages.getMessage("RunAsImplAuthenticationProvider.incorrectKey", - "The presented RunAsUserToken does not contain the expected key")); - } - return authentication; - } - - public String getKey() { - return this.key; - } - - public void setKey(String key) { - this.key = key; - } - - @Override - public void setMessageSource(MessageSource messageSource) { - this.messages = new MessageSourceAccessor(messageSource); - } - - @Override - public boolean supports(Class authentication) { - return RunAsUserToken.class.isAssignableFrom(authentication); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/intercept/RunAsManager.java b/access/src/main/java/org/springframework/security/access/intercept/RunAsManager.java deleted file mode 100644 index bb3b8f8ac20..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/RunAsManager.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import java.util.Collection; - -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.core.Authentication; - -/** - * Creates a new temporary {@link Authentication} object for the current secure object - * invocation only. - * - *

- * This interface permits implementations to replace the Authentication - * object that applies to the current secure object invocation only. The - * {@link org.springframework.security.access.intercept.AbstractSecurityInterceptor} will - * replace the Authentication object held in the - * {@link org.springframework.security.core.context.SecurityContext SecurityContext} for - * the duration of the secure object callback only, returning it to the original - * Authentication object when the callback ends. - *

- * - *

- * This is provided so that systems with two layers of objects can be established. One - * layer is public facing and has normal secure methods with the granted authorities - * expected to be held by external callers. The other layer is private, and is only - * expected to be called by objects within the public facing layer. The objects in this - * private layer still need security (otherwise they would be public methods) and they - * also need security in such a manner that prevents them being called directly by - * external callers. The objects in the private layer would be configured to require - * granted authorities never granted to external callers. The RunAsManager - * interface provides a mechanism to elevate security in this manner. - *

- * - *

- * It is expected implementations will provide a corresponding concrete - * Authentication and AuthenticationProvider so that the - * replacement Authentication object can be authenticated. Some form of - * security will need to be implemented to ensure the AuthenticationProvider - * only accepts Authentication objects created by an authorized concrete - * implementation of RunAsManager. - *

- * - * @author Ben Alex - * @deprecated Authentication is now separated from authorization in Spring Security. This - * class is only used by now-deprecated components. There is not yet an equivalent - * replacement in Spring Security. - */ -@Deprecated -public interface RunAsManager { - - /** - * Returns a replacement Authentication object for the current secure - * object invocation, or null if replacement not required. - * @param authentication the caller invoking the secure object - * @param object the secured object being called - * @param attributes the configuration attributes associated with the secure object - * being invoked - * @return a replacement object to be used for duration of the secure object - * invocation, or null if the Authentication should be left - * as is - */ - Authentication buildRunAs(Authentication authentication, Object object, Collection attributes); - - /** - * Indicates whether this RunAsManager is able to process the passed - * ConfigAttribute. - *

- * This allows the AbstractSecurityInterceptor to check every - * configuration attribute can be consumed by the configured - * AccessDecisionManager and/or RunAsManager and/or - * AfterInvocationManager. - *

- * @param attribute a configuration attribute that has been configured against the - * AbstractSecurityInterceptor - * @return true if this RunAsManager can support the passed - * configuration attribute - */ - boolean supports(ConfigAttribute attribute); - - /** - * Indicates whether the RunAsManager implementation is able to provide - * run-as replacement for the indicated secure object type. - * @param clazz the class that is being queried - * @return true if the implementation can process the indicated class - */ - boolean supports(Class clazz); - -} diff --git a/access/src/main/java/org/springframework/security/access/intercept/RunAsManagerImpl.java b/access/src/main/java/org/springframework/security/access/intercept/RunAsManagerImpl.java deleted file mode 100644 index 67627270be6..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/RunAsManagerImpl.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.util.Assert; - -/** - * Basic concrete implementation of a {@link RunAsManager}. - *

- * Is activated if any {@link ConfigAttribute#getAttribute()} is prefixed with - * RUN_AS_. If found, it generates a new {@link RunAsUserToken} containing - * the same principal, credentials and granted authorities as the original - * {@link Authentication} object, along with {@link SimpleGrantedAuthority}s for each - * RUN_AS_ indicated. The created SimpleGrantedAuthoritys will - * be prefixed with a special prefix indicating that it is a role (default prefix value is - * ROLE_), and then the remainder of the RUN_AS_ keyword. For - * example, RUN_AS_FOO will result in the creation of a granted authority of - * ROLE_RUN_AS_FOO. - *

- * The role prefix may be overridden from the default, to match that used elsewhere, for - * example when using an existing role database with another prefix. An empty role prefix - * may also be specified. Note however that there are potential issues with using an empty - * role prefix since different categories of {@link ConfigAttribute} can not be properly - * discerned based on the prefix, with possible consequences when performing voting and - * other actions. However, this option may be of some use when using pre-existing role - * names without a prefix, and no ability exists to prefix them with a role prefix on - * reading them in, such as provided for example in - * {@link org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl}. - * - * @author Ben Alex - * @author colin sampaleanu - * @deprecated Authentication is now separated from authorization in Spring Security. This - * class is only used by now-deprecated components. There is not yet an equivalent - * replacement in Spring Security. - */ -@NullUnmarked -@Deprecated -public class RunAsManagerImpl implements RunAsManager, InitializingBean { - - @SuppressWarnings("NullAway.Init") - private @Nullable String key; - - private String rolePrefix = "ROLE_"; - - @Override - public void afterPropertiesSet() { - Assert.notNull(this.key, - "A Key is required and should match that configured for the RunAsImplAuthenticationProvider"); - } - - @Override - public @Nullable Authentication buildRunAs(Authentication authentication, Object object, - Collection attributes) { - List newAuthorities = new ArrayList<>(); - for (ConfigAttribute attribute : attributes) { - if (this.supports(attribute)) { - GrantedAuthority extraAuthority = new SimpleGrantedAuthority( - getRolePrefix() + attribute.getAttribute()); - newAuthorities.add(extraAuthority); - } - } - if (newAuthorities.isEmpty()) { - return null; - } - // Add existing authorities - newAuthorities.addAll(authentication.getAuthorities()); - return new RunAsUserToken(this.key, authentication.getPrincipal(), authentication.getCredentials(), - newAuthorities, authentication.getClass()); - } - - public String getKey() { - return this.key; - } - - public String getRolePrefix() { - return this.rolePrefix; - } - - public void setKey(String key) { - this.key = key; - } - - /** - * Allows the default role prefix of ROLE_ to be overridden. May be set - * to an empty value, although this is usually not desirable. - * @param rolePrefix the new prefix - */ - public void setRolePrefix(String rolePrefix) { - this.rolePrefix = rolePrefix; - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return attribute.getAttribute() != null && attribute.getAttribute().startsWith("RUN_AS_"); - } - - /** - * This implementation supports any type of class, because it does not query the - * presented secure object. - * @param clazz the secure object - * @return always true - */ - @Override - public boolean supports(Class clazz) { - return true; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/intercept/RunAsUserToken.java b/access/src/main/java/org/springframework/security/access/intercept/RunAsUserToken.java deleted file mode 100644 index 604e2fd5bfd..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/RunAsUserToken.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import java.util.Collection; - -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; - -/** - * An immutable {@link org.springframework.security.core.Authentication} implementation - * that supports {@link RunAsManagerImpl}. - * - * @author Ben Alex - * @deprecated Authentication is now separated from authorization in Spring Security. This - * class is only used by now-deprecated components. There is not yet an equivalent - * replacement in Spring Security. - */ -@Deprecated -public class RunAsUserToken extends AbstractAuthenticationToken { - - private static final long serialVersionUID = 620L; - - private final Class originalAuthentication; - - private final Object credentials; - - private final Object principal; - - private final int keyHash; - - public RunAsUserToken(String key, Object principal, Object credentials, - Collection authorities, - Class originalAuthentication) { - super(authorities); - this.keyHash = key.hashCode(); - this.principal = principal; - this.credentials = credentials; - this.originalAuthentication = originalAuthentication; - setAuthenticated(true); - } - - @Override - public Object getCredentials() { - return this.credentials; - } - - public int getKeyHash() { - return this.keyHash; - } - - public Class getOriginalAuthentication() { - return this.originalAuthentication; - } - - @Override - public Object getPrincipal() { - return this.principal; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(super.toString()); - String className = (this.originalAuthentication != null) ? this.originalAuthentication.getName() : null; - sb.append("; Original Class: ").append(className); - return sb.toString(); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityInterceptor.java b/access/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityInterceptor.java deleted file mode 100644 index ddd27addec1..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityInterceptor.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept.aopalliance; - -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.security.access.SecurityMetadataSource; -import org.springframework.security.access.intercept.AbstractSecurityInterceptor; -import org.springframework.security.access.intercept.InterceptorStatusToken; -import org.springframework.security.access.method.MethodSecurityMetadataSource; - -/** - * Provides security interception of AOP Alliance based method invocations. - *

- * The SecurityMetadataSource required by this security interceptor is of - * type {@link MethodSecurityMetadataSource}. This is shared with the AspectJ based - * security interceptor (AspectJSecurityInterceptor), since both work with - * Java Methods. - *

- * Refer to {@link AbstractSecurityInterceptor} for details on the workflow. - * - * @author Ben Alex - * @author Rob Winch - * @deprecated Please use - * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor} - * and - * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor} - * instead - */ -@NullUnmarked -@Deprecated -public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements MethodInterceptor { - - private @Nullable MethodSecurityMetadataSource securityMetadataSource; - - @Override - public Class getSecureObjectClass() { - return MethodInvocation.class; - } - - /** - * This method should be used to enforce security on a MethodInvocation. - * @param mi The method being invoked which requires a security decision - * @return The returned value from the method invocation (possibly modified by the - * {@code AfterInvocationManager}). - * @throws Throwable if any error occurs - */ - @Override - public Object invoke(MethodInvocation mi) throws Throwable { - InterceptorStatusToken token = super.beforeInvocation(mi); - Object result; - try { - result = mi.proceed(); - } - finally { - super.finallyInvocation(token); - } - return super.afterInvocation(token, result); - } - - public MethodSecurityMetadataSource getSecurityMetadataSource() { - return this.securityMetadataSource; - } - - @Override - public SecurityMetadataSource obtainSecurityMetadataSource() { - return this.securityMetadataSource; - } - - public void setSecurityMetadataSource(MethodSecurityMetadataSource newSource) { - this.securityMetadataSource = newSource; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityMetadataSourceAdvisor.java b/access/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityMetadataSourceAdvisor.java deleted file mode 100644 index 95275ba6c39..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityMetadataSourceAdvisor.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept.aopalliance; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.Serializable; -import java.lang.reflect.Method; - -import org.aopalliance.aop.Advice; -import org.aopalliance.intercept.MethodInterceptor; -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.aop.Pointcut; -import org.springframework.aop.support.AbstractPointcutAdvisor; -import org.springframework.aop.support.StaticMethodMatcherPointcut; -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.security.access.method.MethodSecurityMetadataSource; -import org.springframework.util.Assert; -import org.springframework.util.CollectionUtils; - -/** - * Advisor driven by a {@link MethodSecurityMetadataSource}, used to exclude a - * {@link MethodInterceptor} from public (non-secure) methods. - *

- * Because the AOP framework caches advice calculations, this is normally faster than just - * letting the MethodInterceptor run and find out itself that it has no work - * to do. - *

- * This class also allows the use of Spring's {@code DefaultAdvisorAutoProxyCreator}, - * which makes configuration easier than setup a ProxyFactoryBean for each - * object requiring security. Note that autoproxying is not supported for BeanFactory - * implementations, as post-processing is automatic only for application contexts. - *

- * Based on Spring's TransactionAttributeSourceAdvisor. - * - * @author Ben Alex - * @author Luke Taylor - * @deprecated Use {@link EnableMethodSecurity} or publish interceptors directly - */ -@NullUnmarked -@Deprecated -@SuppressWarnings("serial") -public class MethodSecurityMetadataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware { - - private transient MethodSecurityMetadataSource attributeSource; - - private transient @Nullable MethodInterceptor interceptor; - - private final Pointcut pointcut = new MethodSecurityMetadataSourcePointcut(); - - private @Nullable BeanFactory beanFactory; - - private final String adviceBeanName; - - private final String metadataSourceBeanName; - - private transient volatile Object adviceMonitor = new Object(); - - /** - * Alternative constructor for situations where we want the advisor decoupled from the - * advice. Instead the advice bean name should be set. This prevents eager - * instantiation of the interceptor (and hence the AuthenticationManager). See - * SEC-773, for example. The metadataSourceBeanName is used rather than a direct - * reference to support serialization via a bean factory lookup. - * @param adviceBeanName name of the MethodSecurityInterceptor bean - * @param attributeSource the SecurityMetadataSource (should be the same as the one - * used on the interceptor) - * @param attributeSourceBeanName the bean name of the attributeSource (required for - * serialization) - */ - public MethodSecurityMetadataSourceAdvisor(String adviceBeanName, MethodSecurityMetadataSource attributeSource, - String attributeSourceBeanName) { - Assert.notNull(adviceBeanName, "The adviceBeanName cannot be null"); - Assert.notNull(attributeSource, "The attributeSource cannot be null"); - Assert.notNull(attributeSourceBeanName, "The attributeSourceBeanName cannot be null"); - this.adviceBeanName = adviceBeanName; - this.attributeSource = attributeSource; - this.metadataSourceBeanName = attributeSourceBeanName; - } - - @Override - public Pointcut getPointcut() { - return this.pointcut; - } - - @Override - public Advice getAdvice() { - synchronized (this.adviceMonitor) { - if (this.interceptor == null) { - Assert.notNull(this.adviceBeanName, "'adviceBeanName' must be set for use with bean factory lookup."); - Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'"); - this.interceptor = this.beanFactory.getBean(this.adviceBeanName, MethodInterceptor.class); - } - return this.interceptor; - } - } - - @Override - public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; - } - - private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { - ois.defaultReadObject(); - this.adviceMonitor = new Object(); - this.attributeSource = this.beanFactory.getBean(this.metadataSourceBeanName, - MethodSecurityMetadataSource.class); - } - - class MethodSecurityMetadataSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { - - @Override - public boolean matches(Method m, Class targetClass) { - MethodSecurityMetadataSource source = MethodSecurityMetadataSourceAdvisor.this.attributeSource; - return !CollectionUtils.isEmpty(source.getAttributes(m, targetClass)); - } - - } - -} diff --git a/access/src/main/java/org/springframework/security/access/intercept/aopalliance/package-info.java b/access/src/main/java/org/springframework/security/access/intercept/aopalliance/package-info.java deleted file mode 100644 index 3067b816adb..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/aopalliance/package-info.java +++ /dev/null @@ -1,24 +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. - */ - -/** - * Enforces security for AOP Alliance MethodInvocations, such as via Spring - * AOP. - */ -@NullMarked -package org.springframework.security.access.intercept.aopalliance; - -import org.jspecify.annotations.NullMarked; diff --git a/access/src/main/java/org/springframework/security/access/intercept/aspectj/AspectJCallback.java b/access/src/main/java/org/springframework/security/access/intercept/aspectj/AspectJCallback.java deleted file mode 100644 index 251992dabb1..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/aspectj/AspectJCallback.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept.aspectj; - -/** - * Called by the {@link AspectJMethodSecurityInterceptor} when it wishes for the AspectJ - * processing to continue. Typically implemented in the around() advice as a - * simple return proceed(); statement. - * - * @author Ben Alex - * @deprecated This class will be removed from the public API. Please either use - * `spring-security-aspects`, Spring Security's method security support or create your own - * class that uses Spring AOP annotations. - */ -@Deprecated -public interface AspectJCallback { - - Object proceedWithObject(); - -} diff --git a/access/src/main/java/org/springframework/security/access/intercept/aspectj/AspectJMethodSecurityInterceptor.java b/access/src/main/java/org/springframework/security/access/intercept/aspectj/AspectJMethodSecurityInterceptor.java deleted file mode 100644 index dcd4ba20f4e..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/aspectj/AspectJMethodSecurityInterceptor.java +++ /dev/null @@ -1,72 +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.access.intercept.aspectj; - -import org.aspectj.lang.JoinPoint; - -import org.springframework.security.access.intercept.InterceptorStatusToken; -import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor; - -/** - * AspectJ {@code JoinPoint} security interceptor which wraps the {@code JoinPoint} in a - * {@code MethodInvocation} adapter to make it compatible with security infrastructure - * classes which only support {@code MethodInvocation}s. - *

- * One of the {@code invoke} methods should be called from the {@code around()} advice in - * your aspect. Alternatively you can use one of the pre-defined aspects from the aspects - * module. - * - * @author Luke Taylor - * @author Rob Winch - * @since 3.0.3 - * @deprecated This class will be removed from the public API. Please either use - * `spring-security-aspects`, Spring Security's method security support or create your own - * class that uses Spring AOP annotations. - */ -@Deprecated -public final class AspectJMethodSecurityInterceptor extends MethodSecurityInterceptor { - - /** - * Method that is suitable for user with @Aspect notation. - * @param jp The AspectJ joint point being invoked which requires a security decision - * @return The returned value from the method invocation - * @throws Throwable if the invocation throws one - */ - public Object invoke(JoinPoint jp) throws Throwable { - return super.invoke(new MethodInvocationAdapter(jp)); - } - - /** - * Method that is suitable for user with traditional AspectJ-code aspects. - * @param jp The AspectJ joint point being invoked which requires a security decision - * @param advisorProceed the advice-defined anonymous class that implements - * {@code AspectJCallback} containing a simple {@code return proceed();} statement - * @return The returned value from the method invocation - */ - public Object invoke(JoinPoint jp, AspectJCallback advisorProceed) { - InterceptorStatusToken token = super.beforeInvocation(new MethodInvocationAdapter(jp)); - Object result; - try { - result = advisorProceed.proceedWithObject(); - } - finally { - super.finallyInvocation(token); - } - return super.afterInvocation(token, result); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/intercept/aspectj/MethodInvocationAdapter.java b/access/src/main/java/org/springframework/security/access/intercept/aspectj/MethodInvocationAdapter.java deleted file mode 100644 index d2f213eb289..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/aspectj/MethodInvocationAdapter.java +++ /dev/null @@ -1,107 +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.access.intercept.aspectj; - -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Method; - -import org.aopalliance.intercept.MethodInvocation; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.reflect.CodeSignature; -import org.jspecify.annotations.NullUnmarked; - -import org.springframework.util.Assert; - -/** - * Decorates a JoinPoint to allow it to be used with method-security infrastructure - * classes which support {@code MethodInvocation} instances. - * - * @author Luke Taylor - * @since 3.0.3 - * @deprecated This class will be removed from the public API. See - * `JoinPointMethodInvocation` in `spring-security-aspects` for its replacement - */ -@NullUnmarked -@Deprecated -public final class MethodInvocationAdapter implements MethodInvocation { - - private final ProceedingJoinPoint jp; - - private final Method method; - - private final Object target; - - MethodInvocationAdapter(JoinPoint jp) { - this.jp = (ProceedingJoinPoint) jp; - if (jp.getTarget() != null) { - this.target = jp.getTarget(); - } - else { - // SEC-1295: target may be null if an ITD is in use - this.target = jp.getSignature().getDeclaringType(); - } - String targetMethodName = jp.getStaticPart().getSignature().getName(); - Class[] types = ((CodeSignature) jp.getStaticPart().getSignature()).getParameterTypes(); - Class declaringType = jp.getStaticPart().getSignature().getDeclaringType(); - this.method = findMethod(targetMethodName, declaringType, types); - Assert.notNull(this.method, () -> "Could not obtain target method from JoinPoint: '" + jp + "'"); - } - - private Method findMethod(String name, Class declaringType, Class[] params) { - Method method = null; - try { - method = declaringType.getMethod(name, params); - } - catch (NoSuchMethodException ignored) { - } - if (method == null) { - try { - method = declaringType.getDeclaredMethod(name, params); - } - catch (NoSuchMethodException ignored) { - } - } - return method; - } - - @Override - public Method getMethod() { - return this.method; - } - - @Override - public Object[] getArguments() { - return this.jp.getArgs(); - } - - @Override - public AccessibleObject getStaticPart() { - return this.method; - } - - @Override - public Object getThis() { - return this.target; - } - - @Override - public Object proceed() throws Throwable { - return this.jp.proceed(); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/intercept/aspectj/package-info.java b/access/src/main/java/org/springframework/security/access/intercept/aspectj/package-info.java deleted file mode 100644 index 1aad36b56ac..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/aspectj/package-info.java +++ /dev/null @@ -1,24 +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. - */ - -/** - * Enforces security for AspectJ JointPoints, delegating secure object - * callbacks to the calling aspect. - */ -@NullMarked -package org.springframework.security.access.intercept.aspectj; - -import org.jspecify.annotations.NullMarked; diff --git a/access/src/main/java/org/springframework/security/access/intercept/package-info.java b/access/src/main/java/org/springframework/security/access/intercept/package-info.java deleted file mode 100644 index 9692138e820..00000000000 --- a/access/src/main/java/org/springframework/security/access/intercept/package-info.java +++ /dev/null @@ -1,39 +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. - */ - -/** - * Abstract level security interception classes which are responsible for enforcing the - * configured security constraints for a secure object. - *

- * A secure object is a term frequently used throughout the security system. It - * does not refer to a business object that is being secured, but instead refers to - * some infrastructure object that can have security facilities provided for it by Spring - * Security. For example, one secure object would be MethodInvocation, whilst - * another would be HTTP {@code org.springframework.security.web.FilterInvocation}. Note - * these are infrastructure objects and their design allows them to represent a large - * variety of actual resources that might need to be secured, such as business objects or - * HTTP request URLs. - *

- * Each secure object typically has its own interceptor package. Each package usually - * includes a concrete security interceptor (which subclasses - * {@link org.springframework.security.access.intercept.AbstractSecurityInterceptor}) and - * an appropriate {@link org.springframework.security.access.SecurityMetadataSource} for - * the type of resources the secure object represents. - */ -@NullMarked -package org.springframework.security.access.intercept; - -import org.jspecify.annotations.NullMarked; diff --git a/access/src/main/java/org/springframework/security/access/method/AbstractFallbackMethodSecurityMetadataSource.java b/access/src/main/java/org/springframework/security/access/method/AbstractFallbackMethodSecurityMetadataSource.java deleted file mode 100644 index e7bbc5bed60..00000000000 --- a/access/src/main/java/org/springframework/security/access/method/AbstractFallbackMethodSecurityMetadataSource.java +++ /dev/null @@ -1,112 +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.access.method; - -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.Collections; - -import org.jspecify.annotations.Nullable; - -import org.springframework.aop.support.AopUtils; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.authorization.AuthorizationManager; - -/** - * Abstract implementation of {@link MethodSecurityMetadataSource} that supports both - * Spring AOP and AspectJ and performs attribute resolution from: 1. specific target - * method; 2. target class; 3. declaring method; 4. declaring class/interface. Use with - * {@link DelegatingMethodSecurityMetadataSource} for caching support. - *

- * This class mimics the behaviour of Spring's - * AbstractFallbackTransactionAttributeSource class. - *

- * Note that this class cannot extract security metadata where that metadata is expressed - * by way of a target method/class (i.e. #1 and #2 above) AND the target method/class is - * encapsulated in another proxy object. Spring Security does not walk a proxy chain to - * locate the concrete/final target object. Consider making Spring Security your final - * advisor (so it advises the final target, as opposed to another proxy), move the - * metadata to declared methods or interfaces the proxy implements, or provide your own - * replacement MethodSecurityMetadataSource. - * - * @author Ben Alex - * @author Luke taylor - * @since 2.0 - * @deprecated Use the {@code use-authorization-manager} attribute for - * {@code } and {@code } instead or use - * annotation-based or {@link AuthorizationManager}-based authorization - */ -@Deprecated -public abstract class AbstractFallbackMethodSecurityMetadataSource extends AbstractMethodSecurityMetadataSource { - - @Override - public Collection getAttributes(Method method, @Nullable Class targetClass) { - // The method may be on an interface, but we need attributes from the target - // class. - // If the target class is null, the method will be unchanged. - Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass); - // First try is the method in the target class. - Collection attr = findAttributes(specificMethod, targetClass); - if (attr != null) { - return attr; - } - // Second try is the config attribute on the target class. - attr = findAttributes(specificMethod.getDeclaringClass()); - if (attr != null) { - return attr; - } - if (specificMethod != method || targetClass == null) { - // Fallback is to look at the original method. - attr = findAttributes(method, method.getDeclaringClass()); - if (attr != null) { - return attr; - } - // Last fallback is the class of the original method. - return findAttributes(method.getDeclaringClass()); - } - return Collections.emptyList(); - } - - /** - * Obtains the security metadata applicable to the specified method invocation. - * - *

- * Note that the {@link Method#getDeclaringClass()} may not equal the - * targetClass. Both parameters are provided to assist subclasses which - * may wish to provide advanced capabilities related to method metadata being - * "registered" against a method even if the target class does not declare the method - * (i.e. the subclass may only inherit the method). - * @param method the method for the current invocation (never null) - * @param targetClass the target class for the invocation (may be null) - * @return the security metadata (or null if no metadata applies) - */ - protected abstract Collection findAttributes(Method method, @Nullable Class targetClass); - - /** - * Obtains the security metadata registered against the specified class. - * - *

- * Subclasses should only return metadata expressed at a class level. Subclasses - * should NOT aggregate metadata for each method registered against a class, as the - * abstract superclass will separate invoke {@link #findAttributes(Method, Class)} for - * individual methods as appropriate. - * @param clazz the target class for the invocation (never null) - * @return the security metadata (or null if no metadata applies) - */ - protected abstract Collection findAttributes(Class clazz); - -} diff --git a/access/src/main/java/org/springframework/security/access/method/AbstractMethodSecurityMetadataSource.java b/access/src/main/java/org/springframework/security/access/method/AbstractMethodSecurityMetadataSource.java deleted file mode 100644 index 4cf3729a5b6..00000000000 --- a/access/src/main/java/org/springframework/security/access/method/AbstractMethodSecurityMetadataSource.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.method; - -import java.util.Collection; - -import org.aopalliance.intercept.MethodInvocation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.NullUnmarked; - -import org.springframework.aop.framework.AopProxyUtils; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.authorization.AuthorizationManager; - -/** - * Abstract implementation of MethodSecurityMetadataSource which resolves the - * secured object type to a MethodInvocation. - * - * @author Ben Alex - * @author Luke Taylor - * @deprecated Use the {@code use-authorization-manager} attribute for - * {@code } and {@code } instead or use - * annotation-based or {@link AuthorizationManager}-based authorization - */ -@NullUnmarked -@Deprecated -public abstract class AbstractMethodSecurityMetadataSource implements MethodSecurityMetadataSource { - - protected final Log logger = LogFactory.getLog(getClass()); - - @Override - public final Collection getAttributes(Object object) { - if (object instanceof MethodInvocation mi) { - Object target = mi.getThis(); - Class targetClass = null; - if (target != null) { - targetClass = (target instanceof Class) ? (Class) target - : AopProxyUtils.ultimateTargetClass(target); - } - Collection attrs = getAttributes(mi.getMethod(), targetClass); - if (attrs != null && !attrs.isEmpty()) { - return attrs; - } - if (target != null && !(target instanceof Class)) { - attrs = getAttributes(mi.getMethod(), target.getClass()); - } - return attrs; - } - throw new IllegalArgumentException("Object must be a non-null MethodInvocation"); - } - - @Override - public final boolean supports(Class clazz) { - return (MethodInvocation.class.isAssignableFrom(clazz)); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/method/DelegatingMethodSecurityMetadataSource.java b/access/src/main/java/org/springframework/security/access/method/DelegatingMethodSecurityMetadataSource.java deleted file mode 100644 index 138e9f97bb6..00000000000 --- a/access/src/main/java/org/springframework/security/access/method/DelegatingMethodSecurityMetadataSource.java +++ /dev/null @@ -1,136 +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.access.method; - -import java.lang.reflect.Method; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.jspecify.annotations.Nullable; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; - -/** - * Automatically tries a series of method definition sources, relying on the first source - * of metadata that provides a non-null/non-empty response. Provides automatic caching of - * the retrieved metadata. - * - * @author Ben Alex - * @author Luke Taylor - * @deprecated Use the {@code use-authorization-manager} attribute for - * {@code } and {@code } instead or use - * annotation-based or {@link AuthorizationManager}-based authorization - */ -@Deprecated -public final class DelegatingMethodSecurityMetadataSource extends AbstractMethodSecurityMetadataSource { - - private static final List NULL_CONFIG_ATTRIBUTE = Collections.emptyList(); - - private final List methodSecurityMetadataSources; - - private final Map> attributeCache = new HashMap<>(); - - public DelegatingMethodSecurityMetadataSource(List methodSecurityMetadataSources) { - Assert.notNull(methodSecurityMetadataSources, "MethodSecurityMetadataSources cannot be null"); - this.methodSecurityMetadataSources = methodSecurityMetadataSources; - } - - @Override - public Collection getAttributes(Method method, @Nullable Class targetClass) { - DefaultCacheKey cacheKey = new DefaultCacheKey(method, targetClass); - synchronized (this.attributeCache) { - Collection cached = this.attributeCache.get(cacheKey); - // Check for canonical value indicating there is no config attribute, - if (cached != null) { - return cached; - } - // No cached value, so query the sources to find a result - Collection attributes = null; - for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) { - attributes = s.getAttributes(method, targetClass); - if (attributes != null && !attributes.isEmpty()) { - break; - } - } - // Put it in the cache. - if (attributes == null || attributes.isEmpty()) { - this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE); - return NULL_CONFIG_ATTRIBUTE; - } - this.logger.debug(LogMessage.format("Caching method [%s] with attributes %s", cacheKey, attributes)); - this.attributeCache.put(cacheKey, attributes); - return attributes; - } - } - - @Override - public Collection getAllConfigAttributes() { - Set set = new HashSet<>(); - for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) { - Collection attrs = s.getAllConfigAttributes(); - if (attrs != null) { - set.addAll(attrs); - } - } - return set; - } - - public List getMethodSecurityMetadataSources() { - return this.methodSecurityMetadataSources; - } - - private static class DefaultCacheKey { - - private final Method method; - - private final @Nullable Class targetClass; - - DefaultCacheKey(Method method, @Nullable Class targetClass) { - this.method = method; - this.targetClass = targetClass; - } - - @Override - public boolean equals(Object other) { - DefaultCacheKey otherKey = (DefaultCacheKey) other; - return (this.method.equals(otherKey.method) - && ObjectUtils.nullSafeEquals(this.targetClass, otherKey.targetClass)); - } - - @Override - public int hashCode() { - return this.method.hashCode() * 21 + ((this.targetClass != null) ? this.targetClass.hashCode() : 0); - } - - @Override - public String toString() { - String targetClassName = (this.targetClass != null) ? this.targetClass.getName() : "-"; - return "CacheKey[" + targetClassName + "; " + this.method + "]"; - } - - } - -} diff --git a/access/src/main/java/org/springframework/security/access/method/MapBasedMethodSecurityMetadataSource.java b/access/src/main/java/org/springframework/security/access/method/MapBasedMethodSecurityMetadataSource.java deleted file mode 100644 index 3ebb24d1414..00000000000 --- a/access/src/main/java/org/springframework/security/access/method/MapBasedMethodSecurityMetadataSource.java +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.method; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.BeanClassLoaderAware; -import org.springframework.core.log.LogMessage; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; - -/** - * Stores a list of ConfigAttributes for a method or class signature. - * - *

- * This class is the preferred implementation of {@link MethodSecurityMetadataSource} for - * XML-based definition of method security metadata. To assist in XML-based definition, - * wildcard support is provided. - *

- * - * @author Ben Alex - * @since 2.0 - * @deprecated Use the {@code use-authorization-manager} attribute for - * {@code } and {@code } instead or use - * annotation-based or {@link AuthorizationManager}-based authorization - */ -@NullUnmarked -@Deprecated -public class MapBasedMethodSecurityMetadataSource extends AbstractFallbackMethodSecurityMetadataSource - implements BeanClassLoaderAware { - - @SuppressWarnings("NullAway") - private @Nullable ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); - - /** - * Map from RegisteredMethod to ConfigAttribute list - */ - protected final Map> methodMap = new HashMap<>(); - - /** - * Map from RegisteredMethod to name pattern used for registration - */ - private final Map nameMap = new HashMap<>(); - - public MapBasedMethodSecurityMetadataSource() { - } - - /** - * Creates the MapBasedMethodSecurityMetadataSource from a - * @param methodMap map of method names to ConfigAttributes. - */ - public MapBasedMethodSecurityMetadataSource(Map> methodMap) { - for (Map.Entry> entry : methodMap.entrySet()) { - addSecureMethod(entry.getKey(), entry.getValue()); - } - } - - /** - * Implementation does not support class-level attributes. - */ - @Override - protected @Nullable Collection findAttributes(Class clazz) { - return null; - } - - /** - * Will walk the method inheritance tree to find the most specific declaration - * applicable. - */ - @Override - protected @Nullable Collection findAttributes(Method method, Class targetClass) { - if (targetClass == null) { - return null; - } - return findAttributesSpecifiedAgainst(method, targetClass); - } - - private @Nullable List findAttributesSpecifiedAgainst(Method method, Class clazz) { - RegisteredMethod registeredMethod = new RegisteredMethod(method, clazz); - if (this.methodMap.containsKey(registeredMethod)) { - return this.methodMap.get(registeredMethod); - } - // Search superclass - if (clazz.getSuperclass() != null) { - return findAttributesSpecifiedAgainst(method, clazz.getSuperclass()); - } - return null; - } - - /** - * Add configuration attributes for a secure method. Method names can end or start - * with * for matching multiple methods. - * @param name type and method name, separated by a dot - * @param attr the security attributes associated with the method - */ - private void addSecureMethod(String name, List attr) { - int lastDotIndex = name.lastIndexOf("."); - Assert.isTrue(lastDotIndex != -1, () -> "'" + name + "' is not a valid method name: format is FQN.methodName"); - String methodName = name.substring(lastDotIndex + 1); - Assert.hasText(methodName, () -> "Method not found for '" + name + "'"); - String typeName = name.substring(0, lastDotIndex); - Class type = ClassUtils.resolveClassName(typeName, this.beanClassLoader); - addSecureMethod(type, methodName, attr); - } - - /** - * Add configuration attributes for a secure method. Mapped method names can end or - * start with * for matching multiple methods. - * @param javaType target interface or class the security configuration attribute - * applies to - * @param mappedName mapped method name, which the javaType has declared or inherited - * @param attr required authorities associated with the method - */ - public void addSecureMethod(Class javaType, String mappedName, List attr) { - String name = javaType.getName() + '.' + mappedName; - this.logger.debug(LogMessage.format("Request to add secure method [%s] with attributes [%s]", name, attr)); - Method[] methods = javaType.getMethods(); - List matchingMethods = new ArrayList<>(); - for (Method method : methods) { - if (method.getName().equals(mappedName) || isMatch(method.getName(), mappedName)) { - matchingMethods.add(method); - } - } - Assert.notEmpty(matchingMethods, () -> "Couldn't find method '" + mappedName + "' on '" + javaType + "'"); - registerAllMatchingMethods(javaType, attr, name, matchingMethods); - } - - private void registerAllMatchingMethods(Class javaType, List attr, String name, - List matchingMethods) { - for (Method method : matchingMethods) { - RegisteredMethod registeredMethod = new RegisteredMethod(method, javaType); - String regMethodName = this.nameMap.get(registeredMethod); - if ((regMethodName == null) || (!regMethodName.equals(name) && (regMethodName.length() <= name.length()))) { - // no already registered method name, or more specific - // method name specification (now) -> (re-)register method - if (regMethodName != null) { - this.logger.debug(LogMessage.format( - "Replacing attributes for secure method [%s]: current name [%s] is more specific than [%s]", - method, name, regMethodName)); - } - this.nameMap.put(registeredMethod, name); - addSecureMethod(registeredMethod, attr); - } - else { - this.logger.debug(LogMessage.format( - "Keeping attributes for secure method [%s]: current name [%s] is not more specific than [%s]", - method, name, regMethodName)); - } - } - } - - /** - * Adds configuration attributes for a specific method, for example where the method - * has been matched using a pointcut expression. If a match already exists in the map - * for the method, then the existing match will be retained, so that if this method is - * called for a more general pointcut it will not override a more specific one which - * has already been added. - *

- * This method should only be called during initialization of the {@code BeanFactory}. - */ - public void addSecureMethod(Class javaType, Method method, List attr) { - RegisteredMethod key = new RegisteredMethod(method, javaType); - if (this.methodMap.containsKey(key)) { - this.logger.debug(LogMessage.format("Method [%s] is already registered with attributes [%s]", method, - this.methodMap.get(key))); - return; - } - this.methodMap.put(key, attr); - } - - /** - * Add configuration attributes for a secure method. - * @param method the method to be secured - * @param attr required authorities associated with the method - */ - private void addSecureMethod(RegisteredMethod method, List attr) { - Assert.notNull(method, "RegisteredMethod required"); - Assert.notNull(attr, "Configuration attribute required"); - this.logger.info(LogMessage.format("Adding secure method [%s] with attributes [%s]", method, attr)); - this.methodMap.put(method, attr); - } - - /** - * Obtains the configuration attributes explicitly defined against this bean. - * @return the attributes explicitly defined against this bean - */ - @Override - public Collection getAllConfigAttributes() { - Set allAttributes = new HashSet<>(); - this.methodMap.values().forEach(allAttributes::addAll); - return allAttributes; - } - - /** - * Return if the given method name matches the mapped name. The default implementation - * checks for "xxx" and "xxx" matches. - * @param methodName the method name of the class - * @param mappedName the name in the descriptor - * @return if the names match - */ - private boolean isMatch(String methodName, String mappedName) { - return (mappedName.endsWith("*") && methodName.startsWith(mappedName.substring(0, mappedName.length() - 1))) - || (mappedName.startsWith("*") && methodName.endsWith(mappedName.substring(1, mappedName.length()))); - } - - @Override - public void setBeanClassLoader(ClassLoader beanClassLoader) { - Assert.notNull(beanClassLoader, "Bean class loader required"); - this.beanClassLoader = beanClassLoader; - } - - /** - * @return map size (for unit tests and diagnostics) - */ - public int getMethodMapSize() { - return this.methodMap.size(); - } - - /** - * Stores both the Java Method as well as the Class we obtained the Method from. This - * is necessary because Method only provides us access to the declaring class. It - * doesn't provide a way for us to introspect which Class the Method was registered - * against. If a given Class inherits and redeclares a method (i.e. calls super();) - * the registered Class and declaring Class are the same. If a given class merely - * inherits but does not redeclare a method, the registered Class will be the Class - * we're invoking against and the Method will provide details of the declared class. - */ - private static class RegisteredMethod { - - private final Method method; - - private final Class registeredJavaType; - - RegisteredMethod(Method method, Class registeredJavaType) { - Assert.notNull(method, "Method required"); - Assert.notNull(registeredJavaType, "Registered Java Type required"); - this.method = method; - this.registeredJavaType = registeredJavaType; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj instanceof RegisteredMethod rhs) { - return this.method.equals(rhs.method) && this.registeredJavaType.equals(rhs.registeredJavaType); - } - return false; - } - - @Override - public int hashCode() { - return this.method.hashCode() * this.registeredJavaType.hashCode(); - } - - @Override - public String toString() { - return "RegisteredMethod[" + this.registeredJavaType.getName() + "; " + this.method + "]"; - } - - } - -} diff --git a/access/src/main/java/org/springframework/security/access/method/MethodSecurityMetadataSource.java b/access/src/main/java/org/springframework/security/access/method/MethodSecurityMetadataSource.java deleted file mode 100644 index 4d875118c44..00000000000 --- a/access/src/main/java/org/springframework/security/access/method/MethodSecurityMetadataSource.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.method; - -import java.lang.reflect.Method; -import java.util.Collection; - -import org.jspecify.annotations.Nullable; - -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityMetadataSource; -import org.springframework.security.authorization.AuthorizationManager; - -/** - * Interface for SecurityMetadataSource implementations that are designed to - * perform lookups keyed on Methods. - * - * @author Ben Alex - * @see org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager - * @see org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager - * @deprecated Use the {@code use-authorization-manager} attribute for - * {@code } and {@code } instead or use - * annotation-based or {@link AuthorizationManager}-based authorization - */ -@Deprecated -public interface MethodSecurityMetadataSource extends SecurityMetadataSource { - - Collection getAttributes(Method method, @Nullable Class targetClass); - -} diff --git a/access/src/main/java/org/springframework/security/access/method/P.java b/access/src/main/java/org/springframework/security/access/method/P.java deleted file mode 100644 index 319642fc930..00000000000 --- a/access/src/main/java/org/springframework/security/access/method/P.java +++ /dev/null @@ -1,49 +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.access.method; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.security.core.parameters.AnnotationParameterNameDiscoverer; - -/** - * An annotation that can be used along with {@link AnnotationParameterNameDiscoverer} to - * specify parameter names. This is useful for interfaces prior to JDK 8 which cannot - * contain the parameter names. - * - * @see AnnotationParameterNameDiscoverer - * @author Rob Winch - * @since 3.2 - * @deprecated use @{code org.springframework.security.core.parameters.P} - */ -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Deprecated -public @interface P { - - /** - * The parameter name - * @return - */ - String value(); - -} diff --git a/access/src/main/java/org/springframework/security/access/method/package-info.java b/access/src/main/java/org/springframework/security/access/method/package-info.java deleted file mode 100644 index 130055aeae1..00000000000 --- a/access/src/main/java/org/springframework/security/access/method/package-info.java +++ /dev/null @@ -1,24 +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. - */ - -/** - * Provides {@code SecurityMetadataSource} implementations for securing Java method - * invocations via different AOP libraries. - */ -@NullMarked -package org.springframework.security.access.method; - -import org.jspecify.annotations.NullMarked; diff --git a/access/src/main/java/org/springframework/security/access/prepost/PostInvocationAdviceProvider.java b/access/src/main/java/org/springframework/security/access/prepost/PostInvocationAdviceProvider.java deleted file mode 100644 index eef23053c61..00000000000 --- a/access/src/main/java/org/springframework/security/access/prepost/PostInvocationAdviceProvider.java +++ /dev/null @@ -1,87 +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.access.prepost; - -import java.util.Collection; - -import org.aopalliance.intercept.MethodInvocation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.AfterInvocationProvider; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.core.Authentication; - -/** - * AfterInvocationProvider which delegates to a - * {@link PostInvocationAuthorizationAdvice} instance passing it the - * PostInvocationAttribute created from @PostAuthorize and @PostFilter - * annotations. - * - * @author Luke Taylor - * @author Alexander Furer - * @since 3.0 - * @deprecated Use - * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor} - * instead - */ -@NullUnmarked -@Deprecated -public class PostInvocationAdviceProvider implements AfterInvocationProvider { - - protected final Log logger = LogFactory.getLog(getClass()); - - private final PostInvocationAuthorizationAdvice postAdvice; - - public PostInvocationAdviceProvider(PostInvocationAuthorizationAdvice postAdvice) { - this.postAdvice = postAdvice; - } - - @Override - public Object decide(Authentication authentication, Object object, Collection config, - Object returnedObject) throws AccessDeniedException { - PostInvocationAttribute postInvocationAttribute = findPostInvocationAttribute(config); - if (postInvocationAttribute == null) { - return returnedObject; - } - return this.postAdvice.after(authentication, (MethodInvocation) object, postInvocationAttribute, - returnedObject); - } - - private @Nullable PostInvocationAttribute findPostInvocationAttribute(Collection config) { - for (ConfigAttribute attribute : config) { - if (attribute instanceof PostInvocationAttribute) { - return (PostInvocationAttribute) attribute; - } - } - return null; - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return attribute instanceof PostInvocationAttribute; - } - - @Override - public boolean supports(Class clazz) { - return MethodInvocation.class.isAssignableFrom(clazz); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/prepost/PostInvocationAttribute.java b/access/src/main/java/org/springframework/security/access/prepost/PostInvocationAttribute.java deleted file mode 100644 index 095fa04edea..00000000000 --- a/access/src/main/java/org/springframework/security/access/prepost/PostInvocationAttribute.java +++ /dev/null @@ -1,36 +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.access.prepost; - -import org.springframework.security.access.ConfigAttribute; - -/** - * Marker interface for attributes which are created from combined @PostFilter - * and @PostAuthorize annotations. - *

- * Consumed by a {@link PostInvocationAuthorizationAdvice}. - * - * @author Luke Taylor - * @since 3.0 - * @deprecated Use - * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor} - * instead - */ -@Deprecated -public interface PostInvocationAttribute extends ConfigAttribute { - -} diff --git a/access/src/main/java/org/springframework/security/access/prepost/PostInvocationAuthorizationAdvice.java b/access/src/main/java/org/springframework/security/access/prepost/PostInvocationAuthorizationAdvice.java deleted file mode 100644 index c5478333be3..00000000000 --- a/access/src/main/java/org/springframework/security/access/prepost/PostInvocationAuthorizationAdvice.java +++ /dev/null @@ -1,40 +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.access.prepost; - -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.aop.framework.AopInfrastructureBean; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.core.Authentication; - -/** - * Performs filtering and authorization logic after a method is invoked. - * - * @author Luke Taylor - * @since 3.0 - * @deprecated Use - * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor} - * instead - */ -@Deprecated -public interface PostInvocationAuthorizationAdvice extends AopInfrastructureBean { - - Object after(Authentication authentication, MethodInvocation mi, PostInvocationAttribute pia, Object returnedObject) - throws AccessDeniedException; - -} diff --git a/access/src/main/java/org/springframework/security/access/prepost/PreInvocationAttribute.java b/access/src/main/java/org/springframework/security/access/prepost/PreInvocationAttribute.java deleted file mode 100644 index 246cab05c1f..00000000000 --- a/access/src/main/java/org/springframework/security/access/prepost/PreInvocationAttribute.java +++ /dev/null @@ -1,36 +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.access.prepost; - -import org.springframework.security.access.ConfigAttribute; - -/** - * Marker interface for attributes which are created from combined @PreFilter - * and @PreAuthorize annotations. - *

- * Consumed by a {@link PreInvocationAuthorizationAdvice}. - * - * @author Luke Taylor - * @since 3.0 - * @deprecated Use - * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor} - * instead - */ -@Deprecated -public interface PreInvocationAttribute extends ConfigAttribute { - -} diff --git a/access/src/main/java/org/springframework/security/access/prepost/PreInvocationAuthorizationAdvice.java b/access/src/main/java/org/springframework/security/access/prepost/PreInvocationAuthorizationAdvice.java deleted file mode 100644 index 3b9238e470d..00000000000 --- a/access/src/main/java/org/springframework/security/access/prepost/PreInvocationAuthorizationAdvice.java +++ /dev/null @@ -1,48 +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.access.prepost; - -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.aop.framework.AopInfrastructureBean; -import org.springframework.security.core.Authentication; - -/** - * Performs argument filtering and authorization logic before a method is invoked. - * - * @author Luke Taylor - * @since 3.0 - * @deprecated Use - * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor} - * instead - */ -@Deprecated -public interface PreInvocationAuthorizationAdvice extends AopInfrastructureBean { - - /** - * The "before" advice which should be executed to perform any filtering necessary and - * to decide whether the method call is authorised. - * @param authentication the information on the principal on whose account the - * decision should be made - * @param mi the method invocation being attempted - * @param preInvocationAttribute the attribute built from the @PreFilter - * and @PostFilter annotations. - * @return true if authorised, false otherwise - */ - boolean before(Authentication authentication, MethodInvocation mi, PreInvocationAttribute preInvocationAttribute); - -} diff --git a/access/src/main/java/org/springframework/security/access/prepost/PreInvocationAuthorizationAdviceVoter.java b/access/src/main/java/org/springframework/security/access/prepost/PreInvocationAuthorizationAdviceVoter.java deleted file mode 100644 index 7c7c8e12867..00000000000 --- a/access/src/main/java/org/springframework/security/access/prepost/PreInvocationAuthorizationAdviceVoter.java +++ /dev/null @@ -1,90 +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.access.prepost; - -import java.util.Collection; - -import org.aopalliance.intercept.MethodInvocation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.core.Authentication; - -/** - * Voter which performs the actions using a PreInvocationAuthorizationAdvice - * implementation generated from @PreFilter and @PreAuthorize annotations. - *

- * In practice, if these annotations are being used, they will normally contain all the - * necessary access control logic, so a voter-based system is not really necessary and a - * single AccessDecisionManager which contained the same logic would suffice. - * However, this class fits in readily with the traditional voter-based - * AccessDecisionManager implementations used by Spring Security. - * - * @author Luke Taylor - * @since 3.0 - * @deprecated Use - * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor} - * instead - */ -@NullUnmarked -@Deprecated -public class PreInvocationAuthorizationAdviceVoter implements AccessDecisionVoter { - - protected final Log logger = LogFactory.getLog(getClass()); - - private final PreInvocationAuthorizationAdvice preAdvice; - - public PreInvocationAuthorizationAdviceVoter(PreInvocationAuthorizationAdvice pre) { - this.preAdvice = pre; - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return attribute instanceof PreInvocationAttribute; - } - - @Override - public boolean supports(Class clazz) { - return MethodInvocation.class.isAssignableFrom(clazz); - } - - @Override - public int vote(Authentication authentication, MethodInvocation method, Collection attributes) { - // Find prefilter and preauth (or combined) attributes - // if both null, abstain else call advice with them - PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes); - if (preAttr == null) { - // No expression based metadata, so abstain - return ACCESS_ABSTAIN; - } - return this.preAdvice.before(authentication, method, preAttr) ? ACCESS_GRANTED : ACCESS_DENIED; - } - - private @Nullable PreInvocationAttribute findPreInvocationAttribute(Collection config) { - for (ConfigAttribute attribute : config) { - if (attribute instanceof PreInvocationAttribute) { - return (PreInvocationAttribute) attribute; - } - } - return null; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/prepost/PrePostAdviceReactiveMethodInterceptor.java b/access/src/main/java/org/springframework/security/access/prepost/PrePostAdviceReactiveMethodInterceptor.java deleted file mode 100644 index c079b5d90de..00000000000 --- a/access/src/main/java/org/springframework/security/access/prepost/PrePostAdviceReactiveMethodInterceptor.java +++ /dev/null @@ -1,195 +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.access.prepost; - -import java.lang.reflect.Method; -import java.util.Collection; - -import kotlinx.coroutines.reactive.ReactiveFlowKt; -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; -import org.reactivestreams.Publisher; -import reactor.core.Exceptions; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.core.KotlinDetector; -import org.springframework.core.MethodParameter; -import org.springframework.core.ReactiveAdapter; -import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.method.MethodSecurityMetadataSource; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.ReactiveSecurityContextHolder; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.util.Assert; - -/** - * A {@link MethodInterceptor} that supports {@link PreAuthorize} and - * {@link PostAuthorize} for methods that return {@link Mono} or {@link Flux} and Kotlin - * coroutine functions. - * - * @author Rob Winch - * @author Eleftheria Stein - * @since 5.0 - * @deprecated Use - * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor} - * or - * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterReactiveMethodInterceptor} - */ -@NullUnmarked -@Deprecated -public class PrePostAdviceReactiveMethodInterceptor implements MethodInterceptor { - - private Authentication anonymous = new AnonymousAuthenticationToken("key", "anonymous", - AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); - - private final MethodSecurityMetadataSource attributeSource; - - private final PreInvocationAuthorizationAdvice preInvocationAdvice; - - private final PostInvocationAuthorizationAdvice postAdvice; - - private static final String COROUTINES_FLOW_CLASS_NAME = "kotlinx.coroutines.flow.Flow"; - - private static final int RETURN_TYPE_METHOD_PARAMETER_INDEX = -1; - - /** - * Creates a new instance - * @param attributeSource the {@link MethodSecurityMetadataSource} to use - * @param preInvocationAdvice the {@link PreInvocationAuthorizationAdvice} to use - * @param postInvocationAdvice the {@link PostInvocationAuthorizationAdvice} to use - */ - public PrePostAdviceReactiveMethodInterceptor(MethodSecurityMetadataSource attributeSource, - PreInvocationAuthorizationAdvice preInvocationAdvice, - PostInvocationAuthorizationAdvice postInvocationAdvice) { - Assert.notNull(attributeSource, "attributeSource cannot be null"); - Assert.notNull(preInvocationAdvice, "preInvocationAdvice cannot be null"); - Assert.notNull(postInvocationAdvice, "postInvocationAdvice cannot be null"); - this.attributeSource = attributeSource; - this.preInvocationAdvice = preInvocationAdvice; - this.postAdvice = postInvocationAdvice; - } - - @Override - public Object invoke(final MethodInvocation invocation) { - Method method = invocation.getMethod(); - Class returnType = method.getReturnType(); - - boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method); - boolean hasFlowReturnType = COROUTINES_FLOW_CLASS_NAME - .equals(new MethodParameter(method, RETURN_TYPE_METHOD_PARAMETER_INDEX).getParameterType().getName()); - boolean hasReactiveReturnType = Publisher.class.isAssignableFrom(returnType) || isSuspendingFunction - || hasFlowReturnType; - - Assert.state(hasReactiveReturnType, - () -> "The returnType " + returnType + " on " + method - + " must return an instance of org.reactivestreams.Publisher " - + "(i.e. Mono / Flux) or the function must be a Kotlin coroutine " - + "function in order to support Reactor Context"); - Class targetClass = invocation.getThis().getClass(); - Collection attributes = this.attributeSource.getAttributes(method, targetClass); - PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes); - // @formatter:off - Mono toInvoke = ReactiveSecurityContextHolder.getContext() - .map(SecurityContext::getAuthentication) - .defaultIfEmpty(this.anonymous) - .filter((auth) -> this.preInvocationAdvice.before(auth, invocation, preAttr)) - .switchIfEmpty(Mono.defer(() -> Mono.error(new AccessDeniedException("Denied")))); - // @formatter:on - PostInvocationAttribute attr = findPostInvocationAttribute(attributes); - if (Mono.class.isAssignableFrom(returnType)) { - return toInvoke.flatMap((auth) -> PrePostAdviceReactiveMethodInterceptor.>proceed(invocation) - .map((r) -> (attr != null) ? this.postAdvice.after(auth, invocation, attr, r) : r)); - } - if (Flux.class.isAssignableFrom(returnType)) { - return toInvoke.flatMapMany((auth) -> PrePostAdviceReactiveMethodInterceptor.>proceed(invocation) - .map((r) -> (attr != null) ? this.postAdvice.after(auth, invocation, attr, r) : r)); - } - if (hasFlowReturnType) { - if (isSuspendingFunction) { - return toInvoke - .flatMapMany((auth) -> Flux.from(PrePostAdviceReactiveMethodInterceptor.proceed(invocation)) - .map((r) -> (attr != null) ? this.postAdvice.after(auth, invocation, attr, r) : r)); - } - else { - ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(returnType); - Assert.state(adapter != null, () -> "The returnType " + returnType + " on " + method - + " must have a org.springframework.core.ReactiveAdapter registered"); - Flux response = toInvoke.flatMapMany((auth) -> Flux - .from(adapter.toPublisher(PrePostAdviceReactiveMethodInterceptor.flowProceed(invocation))) - .map((r) -> (attr != null) ? this.postAdvice.after(auth, invocation, attr, r) : r)); - return KotlinDelegate.asFlow(response); - } - } - return toInvoke.flatMap((auth) -> Mono.from(PrePostAdviceReactiveMethodInterceptor.proceed(invocation)) - .map((r) -> (attr != null) ? this.postAdvice.after(auth, invocation, attr, r) : r)); - } - - private static > @Nullable T proceed(final MethodInvocation invocation) { - try { - return (T) invocation.proceed(); - } - catch (Throwable throwable) { - throw Exceptions.propagate(throwable); - } - } - - private static @Nullable Object flowProceed(final MethodInvocation invocation) { - try { - return invocation.proceed(); - } - catch (Throwable throwable) { - throw Exceptions.propagate(throwable); - } - } - - private static @Nullable PostInvocationAttribute findPostInvocationAttribute(Collection config) { - for (ConfigAttribute attribute : config) { - if (attribute instanceof PostInvocationAttribute) { - return (PostInvocationAttribute) attribute; - } - } - return null; - } - - private static @Nullable PreInvocationAttribute findPreInvocationAttribute(Collection config) { - for (ConfigAttribute attribute : config) { - if (attribute instanceof PreInvocationAttribute) { - return (PreInvocationAttribute) attribute; - } - } - return null; - } - - /** - * Inner class to avoid a hard dependency on Kotlin at runtime. - */ - private static class KotlinDelegate { - - private static Object asFlow(Publisher publisher) { - return ReactiveFlowKt.asFlow(publisher); - } - - } - -} diff --git a/access/src/main/java/org/springframework/security/access/prepost/PrePostAnnotationSecurityMetadataSource.java b/access/src/main/java/org/springframework/security/access/prepost/PrePostAnnotationSecurityMetadataSource.java deleted file mode 100644 index 1dd300590f2..00000000000 --- a/access/src/main/java/org/springframework/security/access/prepost/PrePostAnnotationSecurityMetadataSource.java +++ /dev/null @@ -1,145 +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.access.prepost; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; - -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.log.LogMessage; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.method.AbstractMethodSecurityMetadataSource; -import org.springframework.util.ClassUtils; - -/** - * MethodSecurityMetadataSource which extracts metadata from the @PreFilter - * and @PreAuthorize annotations placed on a method. This class is merely responsible for - * locating the relevant annotations (if any). It delegates the actual - * ConfigAttribute creation to its {@link PrePostInvocationAttributeFactory}, - * thus decoupling itself from the mechanism which will enforce the annotations' - * behaviour. - *

- * Annotations may be specified on classes or methods, and method-specific annotations - * will take precedence. If you use any annotation and do not specify a pre-authorization - * condition, then the method will be allowed as if a @PreAuthorize("permitAll") were - * present. - *

- * Since we are handling multiple annotations here, it's possible that we may have to - * combine annotations defined in multiple locations for a single method - they may be - * defined on the method itself, or at interface or class level. - * - * @author Luke Taylor - * @since 3.0 - * @see PreInvocationAuthorizationAdviceVoter - * @deprecated Use - * {@link org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager} - * and - * {@link org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager} - * instead - */ -@NullUnmarked -@Deprecated -public class PrePostAnnotationSecurityMetadataSource extends AbstractMethodSecurityMetadataSource { - - private final PrePostInvocationAttributeFactory attributeFactory; - - public PrePostAnnotationSecurityMetadataSource(PrePostInvocationAttributeFactory attributeFactory) { - this.attributeFactory = attributeFactory; - } - - @Override - public Collection getAttributes(Method method, Class targetClass) { - if (method.getDeclaringClass() == Object.class) { - return Collections.emptyList(); - } - PreFilter preFilter = findAnnotation(method, targetClass, PreFilter.class); - PreAuthorize preAuthorize = findAnnotation(method, targetClass, PreAuthorize.class); - PostFilter postFilter = findAnnotation(method, targetClass, PostFilter.class); - // TODO: Can we check for void methods and throw an exception here? - PostAuthorize postAuthorize = findAnnotation(method, targetClass, PostAuthorize.class); - if (preFilter == null && preAuthorize == null && postFilter == null && postAuthorize == null) { - // There is no meta-data so return - return Collections.emptyList(); - } - String preFilterAttribute = (preFilter != null) ? preFilter.value() : null; - String filterObject = (preFilter != null) ? preFilter.filterTarget() : null; - String preAuthorizeAttribute = (preAuthorize != null) ? preAuthorize.value() : null; - String postFilterAttribute = (postFilter != null) ? postFilter.value() : null; - String postAuthorizeAttribute = (postAuthorize != null) ? postAuthorize.value() : null; - ArrayList attrs = new ArrayList<>(2); - PreInvocationAttribute pre = this.attributeFactory.createPreInvocationAttribute(preFilterAttribute, - filterObject, preAuthorizeAttribute); - if (pre != null) { - attrs.add(pre); - } - PostInvocationAttribute post = this.attributeFactory.createPostInvocationAttribute(postFilterAttribute, - postAuthorizeAttribute); - if (post != null) { - attrs.add(post); - } - attrs.trimToSize(); - return attrs; - } - - @Override - public @Nullable Collection getAllConfigAttributes() { - return null; - } - - /** - * See - * {@link org.springframework.security.access.method.AbstractFallbackMethodSecurityMetadataSource#getAttributes(Method, Class)} - * for the logic of this method. The ordering here is slightly different in that we - * consider method-specific annotations on an interface before class-level ones. - */ - private @Nullable A findAnnotation(Method method, Class targetClass, - Class annotationClass) { - // The method may be on an interface, but we need attributes from the target - // class. - // If the target class is null, the method will be unchanged. - Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); - A annotation = AnnotationUtils.findAnnotation(specificMethod, annotationClass); - if (annotation != null) { - this.logger.debug(LogMessage.format("%s found on specific method: %s", annotation, specificMethod)); - return annotation; - } - // Check the original (e.g. interface) method - if (specificMethod != method) { - annotation = AnnotationUtils.findAnnotation(method, annotationClass); - if (annotation != null) { - this.logger.debug(LogMessage.format("%s found on: %s", annotation, method)); - return annotation; - } - } - // Check the class-level (note declaringClass, not targetClass, which may not - // actually implement the method) - annotation = AnnotationUtils.findAnnotation(specificMethod.getDeclaringClass(), annotationClass); - if (annotation != null) { - this.logger - .debug(LogMessage.format("%s found on: %s", annotation, specificMethod.getDeclaringClass().getName())); - return annotation; - } - return null; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/prepost/PrePostInvocationAttributeFactory.java b/access/src/main/java/org/springframework/security/access/prepost/PrePostInvocationAttributeFactory.java deleted file mode 100644 index 68715fc9e1c..00000000000 --- a/access/src/main/java/org/springframework/security/access/prepost/PrePostInvocationAttributeFactory.java +++ /dev/null @@ -1,40 +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.access.prepost; - -import org.jspecify.annotations.Nullable; - -import org.springframework.aop.framework.AopInfrastructureBean; -import org.springframework.security.authorization.AuthorizationManager; - -/** - * @author Luke Taylor - * @since 3.0 - * @see org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor - * @see org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor - * @deprecated Use delegation with {@link AuthorizationManager} - */ -@Deprecated -public interface PrePostInvocationAttributeFactory extends AopInfrastructureBean { - - PreInvocationAttribute createPreInvocationAttribute(@Nullable String preFilterAttribute, - @Nullable String filterObject, @Nullable String preAuthorizeAttribute); - - PostInvocationAttribute createPostInvocationAttribute(@Nullable String postFilterAttribute, - @Nullable String postAuthorizeAttribute); - -} diff --git a/access/src/main/java/org/springframework/security/access/vote/AbstractAccessDecisionManager.java b/access/src/main/java/org/springframework/security/access/vote/AbstractAccessDecisionManager.java deleted file mode 100644 index b9b2390cfdb..00000000000 --- a/access/src/main/java/org/springframework/security/access/vote/AbstractAccessDecisionManager.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.vote; - -import java.util.List; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.context.MessageSource; -import org.springframework.context.MessageSourceAware; -import org.springframework.context.support.MessageSourceAccessor; -import org.springframework.security.access.AccessDecisionManager; -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.SpringSecurityMessageSource; -import org.springframework.util.Assert; - -/** - * Abstract implementation of {@link AccessDecisionManager}. - * - *

- * Handles configuration of a bean context defined list of {@link AccessDecisionVoter}s - * and the access control behaviour if all voters abstain from voting (defaults to deny - * access). - * - * @deprecated Use {@link AuthorizationManager} instead - */ -@Deprecated -public abstract class AbstractAccessDecisionManager - implements AccessDecisionManager, InitializingBean, MessageSourceAware { - - protected final Log logger = LogFactory.getLog(getClass()); - - private List> decisionVoters; - - protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); - - private boolean allowIfAllAbstainDecisions = false; - - protected AbstractAccessDecisionManager(List> decisionVoters) { - Assert.notEmpty(decisionVoters, "A list of AccessDecisionVoters is required"); - this.decisionVoters = decisionVoters; - } - - @Override - public void afterPropertiesSet() { - Assert.notEmpty(this.decisionVoters, "A list of AccessDecisionVoters is required"); - Assert.notNull(this.messages, "A message source must be set"); - } - - protected final void checkAllowIfAllAbstainDecisions() { - if (!this.isAllowIfAllAbstainDecisions()) { - throw new AccessDeniedException( - this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); - } - } - - public List> getDecisionVoters() { - return this.decisionVoters; - } - - public boolean isAllowIfAllAbstainDecisions() { - return this.allowIfAllAbstainDecisions; - } - - public void setAllowIfAllAbstainDecisions(boolean allowIfAllAbstainDecisions) { - this.allowIfAllAbstainDecisions = allowIfAllAbstainDecisions; - } - - @Override - public void setMessageSource(MessageSource messageSource) { - this.messages = new MessageSourceAccessor(messageSource); - } - - @Override - public boolean supports(ConfigAttribute attribute) { - for (AccessDecisionVoter voter : this.decisionVoters) { - if (voter.supports(attribute)) { - return true; - } - } - return false; - } - - /** - * Iterates through all AccessDecisionVoters and ensures each can support - * the presented class. - *

- * If one or more voters cannot support the presented class, false is - * returned. - * @param clazz the type of secured object being presented - * @return true if this type is supported - */ - @Override - public boolean supports(Class clazz) { - for (AccessDecisionVoter voter : this.decisionVoters) { - if (!voter.supports(clazz)) { - return false; - } - } - return true; - } - - @Override - public String toString() { - return this.getClass().getSimpleName() + " [DecisionVoters=" + this.decisionVoters - + ", AllowIfAllAbstainDecisions=" + this.allowIfAllAbstainDecisions + "]"; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/vote/AbstractAclVoter.java b/access/src/main/java/org/springframework/security/access/vote/AbstractAclVoter.java deleted file mode 100644 index ec13bdb6267..00000000000 --- a/access/src/main/java/org/springframework/security/access/vote/AbstractAclVoter.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.vote; - -import org.aopalliance.intercept.MethodInvocation; -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.AuthorizationServiceException; -import org.springframework.util.Assert; - -/** - * Provides helper methods for writing domain object ACL voters. Not bound to any - * particular ACL system. - * - * @author Ben Alex - * @deprecated Now used by only-deprecated classes. Generally speaking, in-memory ACL is - * no longer advised, so no replacement is planned at this point. - */ -@NullUnmarked -@Deprecated -public abstract class AbstractAclVoter implements AccessDecisionVoter { - - @SuppressWarnings("NullAway.Init") - private @Nullable Class processDomainObjectClass; - - protected Object getDomainObjectInstance(MethodInvocation invocation) { - Object[] args = invocation.getArguments(); - Class[] params = invocation.getMethod().getParameterTypes(); - for (int i = 0; i < params.length; i++) { - if (this.processDomainObjectClass.isAssignableFrom(params[i])) { - return args[i]; - } - } - throw new AuthorizationServiceException("MethodInvocation: " + invocation - + " did not provide any argument of type: " + this.processDomainObjectClass); - } - - public Class getProcessDomainObjectClass() { - return this.processDomainObjectClass; - } - - public void setProcessDomainObjectClass(Class processDomainObjectClass) { - Assert.notNull(processDomainObjectClass, "processDomainObjectClass cannot be set to null"); - this.processDomainObjectClass = processDomainObjectClass; - } - - /** - * This implementation supports only MethodSecurityInterceptor, because - * it queries the presented MethodInvocation. - * @param clazz the secure object - * @return true if the secure object is MethodInvocation, - * false otherwise - */ - @Override - public boolean supports(Class clazz) { - return (MethodInvocation.class.isAssignableFrom(clazz)); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/vote/AffirmativeBased.java b/access/src/main/java/org/springframework/security/access/vote/AffirmativeBased.java deleted file mode 100644 index eac31e2332a..00000000000 --- a/access/src/main/java/org/springframework/security/access/vote/AffirmativeBased.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.vote; - -import java.util.Collection; -import java.util.List; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.Authentication; - -/** - * Simple concrete implementation of - * {@link org.springframework.security.access.AccessDecisionManager} that grants access if - * any AccessDecisionVoter returns an affirmative response. - * - * @deprecated Use {@link AuthorizationManager} instead - */ -@Deprecated -public class AffirmativeBased extends AbstractAccessDecisionManager { - - public AffirmativeBased(List> decisionVoters) { - super(decisionVoters); - } - - /** - * This concrete implementation simply polls all configured - * {@link AccessDecisionVoter}s and grants access if any - * AccessDecisionVoter voted affirmatively. Denies access only if there - * was a deny vote AND no affirmative votes. - *

- * If every AccessDecisionVoter abstained from voting, the decision will - * be based on the {@link #isAllowIfAllAbstainDecisions()} property (defaults to - * false). - *

- * @param authentication the caller invoking the method - * @param object the secured object - * @param configAttributes the configuration attributes associated with the method - * being invoked - * @throws AccessDeniedException if access is denied - */ - @Override - @SuppressWarnings({ "rawtypes", "unchecked" }) - public void decide(Authentication authentication, Object object, Collection configAttributes) - throws AccessDeniedException { - int deny = 0; - for (AccessDecisionVoter voter : getDecisionVoters()) { - int result = voter.vote(authentication, object, configAttributes); - switch (result) { - case AccessDecisionVoter.ACCESS_GRANTED: - return; - case AccessDecisionVoter.ACCESS_DENIED: - deny++; - break; - default: - break; - } - } - if (deny > 0) { - throw new AccessDeniedException( - this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); - } - // To get this far, every AccessDecisionVoter abstained - checkAllowIfAllAbstainDecisions(); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/vote/AuthenticatedVoter.java b/access/src/main/java/org/springframework/security/access/vote/AuthenticatedVoter.java deleted file mode 100644 index 92736bb5b79..00000000000 --- a/access/src/main/java/org/springframework/security/access/vote/AuthenticatedVoter.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.vote; - -import java.util.Collection; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.authentication.AuthenticationTrustResolver; -import org.springframework.security.authentication.AuthenticationTrustResolverImpl; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * Votes if a {@link ConfigAttribute#getAttribute()} of - * IS_AUTHENTICATED_FULLY or IS_AUTHENTICATED_REMEMBERED or - * IS_AUTHENTICATED_ANONYMOUSLY is present. This list is in order of most - * strict checking to least strict checking. - *

- * The current Authentication will be inspected to determine if the principal - * has a particular level of authentication. The "FULLY" authenticated option means the - * user is authenticated fully (i.e. - * {@link org.springframework.security.authentication.AuthenticationTrustResolver#isAnonymous(Authentication)} - * is false and - * {@link org.springframework.security.authentication.AuthenticationTrustResolver#isRememberMe(Authentication)} - * is false). The "REMEMBERED" will grant access if the principal was either authenticated - * via remember-me OR is fully authenticated. The "ANONYMOUSLY" will grant access if the - * principal was authenticated via remember-me, OR anonymously, OR via full - * authentication. - *

- * All comparisons and prefixes are case sensitive. - * - * @author Ben Alex - * @deprecated Use - * {@link org.springframework.security.authorization.AuthorityAuthorizationManager} - * instead - */ -@Deprecated -public class AuthenticatedVoter implements AccessDecisionVoter { - - public static final String IS_AUTHENTICATED_FULLY = "IS_AUTHENTICATED_FULLY"; - - public static final String IS_AUTHENTICATED_REMEMBERED = "IS_AUTHENTICATED_REMEMBERED"; - - public static final String IS_AUTHENTICATED_ANONYMOUSLY = "IS_AUTHENTICATED_ANONYMOUSLY"; - - private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl(); - - private boolean isFullyAuthenticated(Authentication authentication) { - return this.authenticationTrustResolver.isFullyAuthenticated(authentication); - } - - public void setAuthenticationTrustResolver(AuthenticationTrustResolver authenticationTrustResolver) { - Assert.notNull(authenticationTrustResolver, "AuthenticationTrustResolver cannot be set to null"); - this.authenticationTrustResolver = authenticationTrustResolver; - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return (attribute.getAttribute() != null) && (IS_AUTHENTICATED_FULLY.equals(attribute.getAttribute()) - || IS_AUTHENTICATED_REMEMBERED.equals(attribute.getAttribute()) - || IS_AUTHENTICATED_ANONYMOUSLY.equals(attribute.getAttribute())); - } - - /** - * This implementation supports any type of class, because it does not query the - * presented secure object. - * @param clazz the secure object type - * @return always {@code true} - */ - @Override - public boolean supports(Class clazz) { - return true; - } - - @Override - public int vote(Authentication authentication, Object object, Collection attributes) { - int result = ACCESS_ABSTAIN; - for (ConfigAttribute attribute : attributes) { - if (this.supports(attribute)) { - result = ACCESS_DENIED; - if (IS_AUTHENTICATED_FULLY.equals(attribute.getAttribute())) { - if (isFullyAuthenticated(authentication)) { - return ACCESS_GRANTED; - } - } - if (IS_AUTHENTICATED_REMEMBERED.equals(attribute.getAttribute())) { - if (this.authenticationTrustResolver.isRememberMe(authentication) - || isFullyAuthenticated(authentication)) { - return ACCESS_GRANTED; - } - } - if (IS_AUTHENTICATED_ANONYMOUSLY.equals(attribute.getAttribute())) { - if (this.authenticationTrustResolver.isAnonymous(authentication) - || isFullyAuthenticated(authentication) - || this.authenticationTrustResolver.isRememberMe(authentication)) { - return ACCESS_GRANTED; - } - } - } - } - return result; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/vote/ConsensusBased.java b/access/src/main/java/org/springframework/security/access/vote/ConsensusBased.java deleted file mode 100644 index b84cf64ffea..00000000000 --- a/access/src/main/java/org/springframework/security/access/vote/ConsensusBased.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.vote; - -import java.util.Collection; -import java.util.List; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.Authentication; - -/** - * Simple concrete implementation of - * {@link org.springframework.security.access.AccessDecisionManager} that uses a - * consensus-based approach. - *

- * "Consensus" here means majority-rule (ignoring abstains) rather than unanimous - * agreement (ignoring abstains). If you require unanimity, please see - * {@link UnanimousBased}. - * - * @deprecated Use {@link AuthorizationManager} instead - */ -@Deprecated -public class ConsensusBased extends AbstractAccessDecisionManager { - - private boolean allowIfEqualGrantedDeniedDecisions = true; - - public ConsensusBased(List> decisionVoters) { - super(decisionVoters); - } - - /** - * This concrete implementation simply polls all configured - * {@link AccessDecisionVoter}s and upon completion determines the consensus of - * granted against denied responses. - *

- * If there were an equal number of grant and deny votes, the decision will be based - * on the {@link #isAllowIfEqualGrantedDeniedDecisions()} property (defaults to true). - *

- * If every AccessDecisionVoter abstained from voting, the decision will - * be based on the {@link #isAllowIfAllAbstainDecisions()} property (defaults to - * false). - * @param authentication the caller invoking the method - * @param object the secured object - * @param configAttributes the configuration attributes associated with the method - * being invoked - * @throws AccessDeniedException if access is denied - */ - @Override - @SuppressWarnings({ "rawtypes", "unchecked" }) - public void decide(Authentication authentication, Object object, Collection configAttributes) - throws AccessDeniedException { - int grant = 0; - int deny = 0; - for (AccessDecisionVoter voter : getDecisionVoters()) { - int result = voter.vote(authentication, object, configAttributes); - switch (result) { - case AccessDecisionVoter.ACCESS_GRANTED -> grant++; - case AccessDecisionVoter.ACCESS_DENIED -> deny++; - default -> { - } - } - } - if (grant > deny) { - return; - } - if (deny > grant) { - throw new AccessDeniedException( - this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); - } - if ((grant == deny) && (grant != 0)) { - if (this.allowIfEqualGrantedDeniedDecisions) { - return; - } - throw new AccessDeniedException( - this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); - } - // To get this far, every AccessDecisionVoter abstained - checkAllowIfAllAbstainDecisions(); - } - - public boolean isAllowIfEqualGrantedDeniedDecisions() { - return this.allowIfEqualGrantedDeniedDecisions; - } - - public void setAllowIfEqualGrantedDeniedDecisions(boolean allowIfEqualGrantedDeniedDecisions) { - this.allowIfEqualGrantedDeniedDecisions = allowIfEqualGrantedDeniedDecisions; - } - -} diff --git a/access/src/main/java/org/springframework/security/access/vote/RoleHierarchyVoter.java b/access/src/main/java/org/springframework/security/access/vote/RoleHierarchyVoter.java deleted file mode 100644 index cabf2a2e800..00000000000 --- a/access/src/main/java/org/springframework/security/access/vote/RoleHierarchyVoter.java +++ /dev/null @@ -1,59 +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.access.vote; - -import java.util.Collection; - -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.security.access.hierarchicalroles.RoleHierarchy; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.util.Assert; - -/** - * Extended RoleVoter which uses a {@link RoleHierarchy} definition to determine the roles - * allocated to the current user before voting. - * - * @author Luke Taylor - * @since 2.0.4 - * @deprecated Use - * {@link org.springframework.security.authorization.AuthorityAuthorizationManager#setRoleHierarchy} - * instead - */ -@NullUnmarked -@Deprecated -public class RoleHierarchyVoter extends RoleVoter { - - @SuppressWarnings("NullAway") - private @Nullable RoleHierarchy roleHierarchy = null; - - public RoleHierarchyVoter(RoleHierarchy roleHierarchy) { - Assert.notNull(roleHierarchy, "RoleHierarchy must not be null"); - this.roleHierarchy = roleHierarchy; - } - - /** - * Calls the RoleHierarchy to obtain the complete set of user authorities. - */ - @Override - Collection extractAuthorities(Authentication authentication) { - return this.roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities()); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/vote/RoleVoter.java b/access/src/main/java/org/springframework/security/access/vote/RoleVoter.java deleted file mode 100644 index 86a6ffe8282..00000000000 --- a/access/src/main/java/org/springframework/security/access/vote/RoleVoter.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.vote; - -import java.util.Collection; - -import org.jspecify.annotations.NullUnmarked; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; - -/** - * Votes if any {@link ConfigAttribute#getAttribute()} starts with a prefix indicating - * that it is a role. The default prefix string is ROLE_, but this may be - * overridden to any value. It may also be set to empty, which means that essentially any - * attribute will be voted on. As described further below, the effect of an empty prefix - * may not be quite desirable. - *

- * Abstains from voting if no configuration attribute commences with the role prefix. - * Votes to grant access if there is an exact matching - * {@link org.springframework.security.core.GrantedAuthority} to a - * ConfigAttribute starting with the role prefix. Votes to deny access if - * there is no exact matching GrantedAuthority to a - * ConfigAttribute starting with the role prefix. - *

- * An empty role prefix means that the voter will vote for every ConfigAttribute. When - * there are different categories of ConfigAttributes used, this will not be optimal since - * the voter will be voting for attributes which do not represent roles. However, this - * option may be of some use when using pre-existing role names without a prefix, and no - * ability exists to prefix them with a role prefix on reading them in, such as provided - * for example in {@link org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl}. - *

- * All comparisons and prefixes are case sensitive. - * - * @author Ben Alex - * @author colin sampaleanu - * @deprecated Use - * {@link org.springframework.security.authorization.AuthorityAuthorizationManager} - * instead - */ -@Deprecated -@NullUnmarked -public class RoleVoter implements AccessDecisionVoter { - - private String rolePrefix = "ROLE_"; - - public String getRolePrefix() { - return this.rolePrefix; - } - - /** - * Allows the default role prefix of ROLE_ to be overridden. May be set - * to an empty value, although this is usually not desirable. - * @param rolePrefix the new prefix - */ - public void setRolePrefix(String rolePrefix) { - this.rolePrefix = rolePrefix; - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return (attribute.getAttribute() != null) && attribute.getAttribute().startsWith(getRolePrefix()); - } - - /** - * This implementation supports any type of class, because it does not query the - * presented secure object. - * @param clazz the secure object - * @return always true - */ - @Override - public boolean supports(Class clazz) { - return true; - } - - @Override - public int vote(Authentication authentication, Object object, Collection attributes) { - if (authentication == null) { - return ACCESS_DENIED; - } - int result = ACCESS_ABSTAIN; - Collection authorities = extractAuthorities(authentication); - for (ConfigAttribute attribute : attributes) { - if (this.supports(attribute)) { - result = ACCESS_DENIED; - // Attempt to find a matching granted authority - for (GrantedAuthority authority : authorities) { - if (attribute.getAttribute().equals(authority.getAuthority())) { - return ACCESS_GRANTED; - } - } - } - } - return result; - } - - Collection extractAuthorities(Authentication authentication) { - return authentication.getAuthorities(); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/vote/UnanimousBased.java b/access/src/main/java/org/springframework/security/access/vote/UnanimousBased.java deleted file mode 100644 index c28f5200cc1..00000000000 --- a/access/src/main/java/org/springframework/security/access/vote/UnanimousBased.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.vote; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.Authentication; - -/** - * Simple concrete implementation of - * {@link org.springframework.security.access.AccessDecisionManager} that requires all - * voters to abstain or grant access. - * - * @deprecated Use {@link AuthorizationManager} instead - */ -@Deprecated -public class UnanimousBased extends AbstractAccessDecisionManager { - - public UnanimousBased(List> decisionVoters) { - super(decisionVoters); - } - - /** - * This concrete implementation polls all configured {@link AccessDecisionVoter}s for - * each {@link ConfigAttribute} and grants access if only grant (or abstain) - * votes were received. - *

- * Other voting implementations usually pass the entire list of - * ConfigAttributes to the AccessDecisionVoter. This - * implementation differs in that each AccessDecisionVoter knows only - * about a single ConfigAttribute at a time. - *

- * If every AccessDecisionVoter abstained from voting, the decision will - * be based on the {@link #isAllowIfAllAbstainDecisions()} property (defaults to - * false). - * @param authentication the caller invoking the method - * @param object the secured object - * @param attributes the configuration attributes associated with the method being - * invoked - * @throws AccessDeniedException if access is denied - */ - @Override - @SuppressWarnings({ "rawtypes", "unchecked" }) - public void decide(Authentication authentication, Object object, Collection attributes) - throws AccessDeniedException { - int grant = 0; - List singleAttributeList = new ArrayList<>(1); - singleAttributeList.add(null); - for (ConfigAttribute attribute : attributes) { - singleAttributeList.set(0, attribute); - for (AccessDecisionVoter voter : getDecisionVoters()) { - int result = voter.vote(authentication, object, singleAttributeList); - switch (result) { - case AccessDecisionVoter.ACCESS_GRANTED: - grant++; - break; - case AccessDecisionVoter.ACCESS_DENIED: - throw new AccessDeniedException(this.messages - .getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); - default: - break; - } - } - } - // To get this far, there were no deny votes - if (grant > 0) { - return; - } - // To get this far, every AccessDecisionVoter abstained - checkAllowIfAllAbstainDecisions(); - } - -} diff --git a/access/src/main/java/org/springframework/security/access/vote/package-info.java b/access/src/main/java/org/springframework/security/access/vote/package-info.java deleted file mode 100644 index b288bbae914..00000000000 --- a/access/src/main/java/org/springframework/security/access/vote/package-info.java +++ /dev/null @@ -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. - */ - -/** - * Implements a vote-based approach to authorization decisions. - */ -@NullMarked -package org.springframework.security.access.vote; - -import org.jspecify.annotations.NullMarked; diff --git a/access/src/main/java/org/springframework/security/acls/AclEntryVoter.java b/access/src/main/java/org/springframework/security/acls/AclEntryVoter.java deleted file mode 100644 index 845024154c9..00000000000 --- a/access/src/main/java/org/springframework/security/acls/AclEntryVoter.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import org.aopalliance.intercept.MethodInvocation; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.security.access.AuthorizationServiceException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.vote.AbstractAclVoter; -import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl; -import org.springframework.security.acls.domain.SidRetrievalStrategyImpl; -import org.springframework.security.acls.model.Acl; -import org.springframework.security.acls.model.AclService; -import org.springframework.security.acls.model.NotFoundException; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; -import org.springframework.security.acls.model.Permission; -import org.springframework.security.acls.model.Sid; -import org.springframework.security.acls.model.SidRetrievalStrategy; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; - -/** - *

- * Given a domain object instance passed as a method argument, ensures the principal has - * appropriate permission as indicated by the {@link AclService}. - *

- * The AclService is used to retrieve the access control list (ACL) permissions - * associated with a domain object instance for the current Authentication - * object. - *

- * The voter will vote if any {@link ConfigAttribute#getAttribute()} matches the - * {@link #processConfigAttribute}. The provider will then locate the first method - * argument of type {@link #processDomainObjectClass}. Assuming that method argument is - * non-null, the provider will then lookup the ACLs from the AclManager and - * ensure the principal is {@link Acl#isGranted(List, List, boolean)} when presenting the - * {@link #requirePermission} array to that method. - *

- * If the method argument is null, the voter will abstain from voting. If the - * method argument could not be found, an {@link AuthorizationServiceException} will be - * thrown. - *

- * In practical terms users will typically setup a number of AclEntryVoters. Each - * will have a different {@link #setProcessDomainObjectClass processDomainObjectClass}, - * {@link #processConfigAttribute} and {@link #requirePermission} combination. For - * example, a small application might employ the following instances of - * AclEntryVoter: - *

    - *
  • Process domain object class BankAccount, configuration attribute - * VOTE_ACL_BANK_ACCOUNT_READ, require permission - * BasePermission.READ
  • - *
  • Process domain object class BankAccount, configuration attribute - * VOTE_ACL_BANK_ACCOUNT_WRITE, require permission list - * BasePermission.WRITE and BasePermission.CREATE (allowing the - * principal to have either of these two permissions)
  • - *
  • Process domain object class Customer, configuration attribute - * VOTE_ACL_CUSTOMER_READ, require permission - * BasePermission.READ
  • - *
  • Process domain object class Customer, configuration attribute - * VOTE_ACL_CUSTOMER_WRITE, require permission list - * BasePermission.WRITE and BasePermission.CREATE
  • - *
- * Alternatively, you could have used a common superclass or interface for the - * {@link #processDomainObjectClass} if both BankAccount and - * Customer had common parents. - * - *

- * If the principal does not have sufficient permissions, the voter will vote to deny - * access. - * - *

- * All comparisons and prefixes are case sensitive. - * - * @author Ben Alex - * @deprecated please use {@link AclPermissionEvaluator} instead. Spring Method Security - * annotations may also prove useful, for example - * {@code @PreAuthorize("hasPermission(#id, ObjectsReturnType.class, read)")} - */ -@Deprecated -public class AclEntryVoter extends AbstractAclVoter { - - private static final Log logger = LogFactory.getLog(AclEntryVoter.class); - - private final AclService aclService; - - private final String processConfigAttribute; - - private final List requirePermission; - - private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl(); - - private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl(); - - private String internalMethod; - - public AclEntryVoter(AclService aclService, String processConfigAttribute, Permission[] requirePermission) { - Assert.notNull(processConfigAttribute, "A processConfigAttribute is mandatory"); - Assert.notNull(aclService, "An AclService is mandatory"); - Assert.isTrue(!ObjectUtils.isEmpty(requirePermission), "One or more requirePermission entries is mandatory"); - this.aclService = aclService; - this.processConfigAttribute = processConfigAttribute; - this.requirePermission = Arrays.asList(requirePermission); - } - - /** - * Optionally specifies a method of the domain object that will be used to obtain a - * contained domain object. That contained domain object will be used for the ACL - * evaluation. This is useful if a domain object contains a parent that an ACL - * evaluation should be targeted for, instead of the child domain object (which - * perhaps is being created and as such does not yet have any ACL permissions) - * @return null to use the domain object, or the name of a method (that - * requires no arguments) that should be invoked to obtain an Object - * which will be the domain object used for ACL evaluation - */ - protected String getInternalMethod() { - return this.internalMethod; - } - - public void setInternalMethod(String internalMethod) { - this.internalMethod = internalMethod; - } - - protected String getProcessConfigAttribute() { - return this.processConfigAttribute; - } - - public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) { - Assert.notNull(objectIdentityRetrievalStrategy, "ObjectIdentityRetrievalStrategy required"); - this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy; - } - - public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) { - Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required"); - this.sidRetrievalStrategy = sidRetrievalStrategy; - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return (attribute.getAttribute() != null) && attribute.getAttribute().equals(getProcessConfigAttribute()); - } - - @Override - public int vote(Authentication authentication, MethodInvocation object, Collection attributes) { - for (ConfigAttribute attr : attributes) { - if (!supports(attr)) { - continue; - } - - // Need to make an access decision on this invocation - // Attempt to locate the domain object instance to process - Object domainObject = getDomainObjectInstance(object); - - // If domain object is null, vote to abstain - if (domainObject == null) { - logger.debug("Voting to abstain - domainObject is null"); - return ACCESS_ABSTAIN; - } - - // Evaluate if we are required to use an inner domain object - if (StringUtils.hasText(this.internalMethod)) { - domainObject = invokeInternalMethod(domainObject); - } - - // Obtain the OID applicable to the domain object - ObjectIdentity objectIdentity = this.objectIdentityRetrievalStrategy.getObjectIdentity(domainObject); - - // Obtain the SIDs applicable to the principal - List sids = this.sidRetrievalStrategy.getSids(authentication); - - Acl acl; - - try { - // Lookup only ACLs for SIDs we're interested in - acl = this.aclService.readAclById(objectIdentity, sids); - } - catch (NotFoundException ex) { - logger.debug("Voting to deny access - no ACLs apply for this principal"); - return ACCESS_DENIED; - } - - try { - if (acl.isGranted(this.requirePermission, sids, false)) { - logger.debug("Voting to grant access"); - return ACCESS_GRANTED; - } - logger.debug("Voting to deny access - ACLs returned, but insufficient permissions for this principal"); - return ACCESS_DENIED; - } - catch (NotFoundException ex) { - logger.debug("Voting to deny access - no ACLs apply for this principal"); - return ACCESS_DENIED; - } - } - - // No configuration attribute matched, so abstain - return ACCESS_ABSTAIN; - } - - private Object invokeInternalMethod(Object domainObject) { - try { - Class domainObjectType = domainObject.getClass(); - Method method = domainObjectType.getMethod(this.internalMethod, new Class[0]); - return method.invoke(domainObject); - } - catch (NoSuchMethodException ex) { - throw new AuthorizationServiceException("Object of class '" + domainObject.getClass() - + "' does not provide the requested internalMethod: " + this.internalMethod); - } - catch (IllegalAccessException ex) { - logger.debug("IllegalAccessException", ex); - throw new AuthorizationServiceException( - "Problem invoking internalMethod: " + this.internalMethod + " for object: " + domainObject); - } - catch (InvocationTargetException ex) { - logger.debug("InvocationTargetException", ex); - throw new AuthorizationServiceException( - "Problem invoking internalMethod: " + this.internalMethod + " for object: " + domainObject); - } - } - -} diff --git a/access/src/main/java/org/springframework/security/acls/afterinvocation/AbstractAclProvider.java b/access/src/main/java/org/springframework/security/acls/afterinvocation/AbstractAclProvider.java deleted file mode 100644 index 0a8e24a4b3c..00000000000 --- a/access/src/main/java/org/springframework/security/acls/afterinvocation/AbstractAclProvider.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.afterinvocation; - -import java.util.List; - -import org.springframework.security.access.AfterInvocationProvider; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.acls.AclPermissionEvaluator; -import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl; -import org.springframework.security.acls.domain.SidRetrievalStrategyImpl; -import org.springframework.security.acls.model.Acl; -import org.springframework.security.acls.model.AclService; -import org.springframework.security.acls.model.NotFoundException; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; -import org.springframework.security.acls.model.Permission; -import org.springframework.security.acls.model.Sid; -import org.springframework.security.acls.model.SidRetrievalStrategy; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; - -/** - * Abstract {@link AfterInvocationProvider} which provides commonly-used ACL-related - * services. - * - * @author Ben Alex - * @deprecated please use {@link AclPermissionEvaluator} instead. Spring Method Security - * annotations may also prove useful, for example - * {@code @PostAuthorize("hasPermission(filterObject, read)")} - */ -@Deprecated -public abstract class AbstractAclProvider implements AfterInvocationProvider { - - protected final AclService aclService; - - protected String processConfigAttribute; - - protected Class processDomainObjectClass = Object.class; - - protected ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl(); - - protected SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl(); - - protected final List requirePermission; - - public AbstractAclProvider(AclService aclService, String processConfigAttribute, - List requirePermission) { - Assert.hasText(processConfigAttribute, "A processConfigAttribute is mandatory"); - Assert.notNull(aclService, "An AclService is mandatory"); - Assert.isTrue(!ObjectUtils.isEmpty(requirePermission), "One or more requirePermission entries is mandatory"); - this.aclService = aclService; - this.processConfigAttribute = processConfigAttribute; - this.requirePermission = requirePermission; - } - - protected Class getProcessDomainObjectClass() { - return this.processDomainObjectClass; - } - - protected boolean hasPermission(Authentication authentication, Object domainObject) { - // Obtain the OID applicable to the domain object - ObjectIdentity objectIdentity = this.objectIdentityRetrievalStrategy.getObjectIdentity(domainObject); - - // Obtain the SIDs applicable to the principal - List sids = this.sidRetrievalStrategy.getSids(authentication); - - try { - // Lookup only ACLs for SIDs we're interested in - Acl acl = this.aclService.readAclById(objectIdentity, sids); - return acl.isGranted(this.requirePermission, sids, false); - } - catch (NotFoundException ex) { - return false; - } - } - - public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) { - Assert.notNull(objectIdentityRetrievalStrategy, "ObjectIdentityRetrievalStrategy required"); - this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy; - } - - protected void setProcessConfigAttribute(String processConfigAttribute) { - Assert.hasText(processConfigAttribute, "A processConfigAttribute is mandatory"); - this.processConfigAttribute = processConfigAttribute; - } - - public void setProcessDomainObjectClass(Class processDomainObjectClass) { - Assert.notNull(processDomainObjectClass, "processDomainObjectClass cannot be set to null"); - this.processDomainObjectClass = processDomainObjectClass; - } - - public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) { - Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required"); - this.sidRetrievalStrategy = sidRetrievalStrategy; - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return this.processConfigAttribute.equals(attribute.getAttribute()); - } - - /** - * This implementation supports any type of class, because it does not query the - * presented secure object. - * @param clazz the secure object - * @return always true - */ - @Override - public boolean supports(Class clazz) { - return true; - } - -} diff --git a/access/src/main/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationCollectionFilteringProvider.java b/access/src/main/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationCollectionFilteringProvider.java deleted file mode 100644 index 8f3adb1f5ee..00000000000 --- a/access/src/main/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationCollectionFilteringProvider.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.afterinvocation; - -import java.util.Collection; -import java.util.List; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.AuthorizationServiceException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.acls.AclPermissionEvaluator; -import org.springframework.security.acls.model.AclService; -import org.springframework.security.acls.model.Permission; -import org.springframework.security.core.Authentication; - -/** - *

- * Given a Collection of domain object instances returned from a secure - * object invocation, remove any Collection elements the principal does not - * have appropriate permission to access as defined by the {@link AclService}. - *

- * The AclService is used to retrieve the access control list (ACL) - * permissions associated with each Collection domain object instance element - * for the current Authentication object. - *

- * This after invocation provider will fire if any {@link ConfigAttribute#getAttribute()} - * matches the {@link #processConfigAttribute}. The provider will then lookup the ACLs - * from the AclService and ensure the principal is - * {@link org.springframework.security.acls.model.Acl#isGranted(List, List, boolean) - * Acl.isGranted()} when presenting the {@link #requirePermission} array to that method. - *

- * If the principal does not have permission, that element will not be included in the - * returned Collection. - *

- * Often users will setup a BasicAclEntryAfterInvocationProvider with a - * {@link #processConfigAttribute} of AFTER_ACL_COLLECTION_READ and a - * {@link #requirePermission} of BasePermission.READ. These are also the - * defaults. - *

- * If the provided returnObject is null, a null - * Collection will be returned. If the provided returnObject is - * not a Collection, an {@link AuthorizationServiceException} will be thrown. - *

- * All comparisons and prefixes are case sensitive. - * - * @author Ben Alex - * @author Paulo Neves - * @deprecated please use {@link AclPermissionEvaluator} instead. Spring Method Security - * annotations may also prove useful, for example - * {@code @PostFilter("hasPermission(filterObject, read)")} - */ -@Deprecated -public class AclEntryAfterInvocationCollectionFilteringProvider extends AbstractAclProvider { - - protected static final Log logger = LogFactory.getLog(AclEntryAfterInvocationCollectionFilteringProvider.class); - - public AclEntryAfterInvocationCollectionFilteringProvider(AclService aclService, - List requirePermission) { - super(aclService, "AFTER_ACL_COLLECTION_READ", requirePermission); - } - - @Override - @SuppressWarnings("unchecked") - public Object decide(Authentication authentication, Object object, Collection config, - Object returnedObject) throws AccessDeniedException { - if (returnedObject == null) { - logger.debug("Return object is null, skipping"); - return null; - } - - for (ConfigAttribute attr : config) { - if (!this.supports(attr)) { - continue; - } - - // Need to process the Collection for this invocation - Filterer filterer = getFilterer(returnedObject); - - // Locate unauthorised Collection elements - for (Object domainObject : filterer) { - // Ignore nulls or entries which aren't instances of the configured domain - // object class - if (domainObject == null || !getProcessDomainObjectClass().isAssignableFrom(domainObject.getClass())) { - continue; - } - if (!hasPermission(authentication, domainObject)) { - filterer.remove(domainObject); - logger.debug(LogMessage.of(() -> "Principal is NOT authorised for element: " + domainObject)); - } - } - return filterer.getFilteredObject(); - } - return returnedObject; - } - - private Filterer getFilterer(Object returnedObject) { - if (returnedObject instanceof Collection) { - return new CollectionFilterer((Collection) returnedObject); - } - if (returnedObject.getClass().isArray()) { - return new ArrayFilterer((Object[]) returnedObject); - } - throw new AuthorizationServiceException("A Collection or an array (or null) was required as the " - + "returnedObject, but the returnedObject was: " + returnedObject); - } - -} diff --git a/access/src/main/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationProvider.java b/access/src/main/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationProvider.java deleted file mode 100644 index 6142d45cb91..00000000000 --- a/access/src/main/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationProvider.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.afterinvocation; - -import java.util.Collection; -import java.util.List; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.context.MessageSource; -import org.springframework.context.MessageSourceAware; -import org.springframework.context.support.MessageSourceAccessor; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.acls.AclPermissionEvaluator; -import org.springframework.security.acls.model.AclService; -import org.springframework.security.acls.model.Permission; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.SpringSecurityMessageSource; - -/** - * Given a domain object instance returned from a secure object invocation, ensures the - * principal has appropriate permission as defined by the {@link AclService}. - *

- * The AclService is used to retrieve the access control list (ACL) - * permissions associated with a domain object instance for the current - * Authentication object. - *

- * This after invocation provider will fire if any {@link ConfigAttribute#getAttribute()} - * matches the {@link #processConfigAttribute}. The provider will then lookup the ACLs - * from the AclService and ensure the principal is - * {@link org.springframework.security.acls.model.Acl#isGranted(List, List, boolean) - * Acl.isGranted(List, List, boolean)} when presenting the {@link #requirePermission} - * array to that method. - *

- * Often users will set up an AclEntryAfterInvocationProvider with a - * {@link #processConfigAttribute} of AFTER_ACL_READ and a - * {@link #requirePermission} of BasePermission.READ. These are also the - * defaults. - *

- * If the principal does not have sufficient permissions, an - * AccessDeniedException will be thrown. - *

- * If the provided returnedObject is null, permission will always be - * granted and null will be returned. - *

- * All comparisons and prefixes are case sensitive. - * - * @deprecated please use {@link AclPermissionEvaluator} instead. Spring Method Security - * annotations may also prove useful, for example - * {@code @PostAuthorize("hasPermission(filterObject, read)")} - */ -@Deprecated -public class AclEntryAfterInvocationProvider extends AbstractAclProvider implements MessageSourceAware { - - protected static final Log logger = LogFactory.getLog(AclEntryAfterInvocationProvider.class); - - protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); - - public AclEntryAfterInvocationProvider(AclService aclService, List requirePermission) { - this(aclService, "AFTER_ACL_READ", requirePermission); - } - - public AclEntryAfterInvocationProvider(AclService aclService, String processConfigAttribute, - List requirePermission) { - super(aclService, processConfigAttribute, requirePermission); - } - - @Override - public Object decide(Authentication authentication, Object object, Collection config, - Object returnedObject) throws AccessDeniedException { - - if (returnedObject == null) { - // AclManager interface contract prohibits nulls - // As they have permission to null/nothing, grant access - logger.debug("Return object is null, skipping"); - return null; - } - - if (!getProcessDomainObjectClass().isAssignableFrom(returnedObject.getClass())) { - logger.debug("Return object is not applicable for this provider, skipping"); - return returnedObject; - } - - for (ConfigAttribute attr : config) { - if (!this.supports(attr)) { - continue; - } - - // Need to make an access decision on this invocation - if (hasPermission(authentication, returnedObject)) { - return returnedObject; - } - - logger.debug("Denying access"); - throw new AccessDeniedException(this.messages.getMessage("AclEntryAfterInvocationProvider.noPermission", - new Object[] { authentication.getName(), returnedObject }, - "Authentication {0} has NO permissions to the domain object {1}")); - } - - return returnedObject; - } - - @Override - public void setMessageSource(MessageSource messageSource) { - this.messages = new MessageSourceAccessor(messageSource); - } - -} diff --git a/access/src/main/java/org/springframework/security/acls/afterinvocation/ArrayFilterer.java b/access/src/main/java/org/springframework/security/acls/afterinvocation/ArrayFilterer.java deleted file mode 100644 index 5b5e84fa415..00000000000 --- a/access/src/main/java/org/springframework/security/acls/afterinvocation/ArrayFilterer.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.afterinvocation; - -import java.lang.reflect.Array; -import java.util.HashSet; -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; - -/** - * A filter used to filter arrays. - * - * @author Ben Alex - * @author Paulo Neves - * @deprecated please see {@code PostFilter} - */ -@Deprecated -class ArrayFilterer implements Filterer { - - protected static final Log logger = LogFactory.getLog(ArrayFilterer.class); - - private final Set removeList; - - private final T[] list; - - ArrayFilterer(T[] list) { - this.list = list; - // Collect the removed objects to a HashSet so that - // it is fast to lookup them when a filtered array - // is constructed. - this.removeList = new HashSet<>(); - } - - @Override - @SuppressWarnings("unchecked") - public T[] getFilteredObject() { - // Recreate an array of same type and filter the removed objects. - int originalSize = this.list.length; - int sizeOfResultingList = originalSize - this.removeList.size(); - T[] filtered = (T[]) Array.newInstance(this.list.getClass().getComponentType(), sizeOfResultingList); - for (int i = 0, j = 0; i < this.list.length; i++) { - T object = this.list[i]; - if (!this.removeList.contains(object)) { - filtered[j] = object; - j++; - } - } - logger.debug(LogMessage.of(() -> "Original array contained " + originalSize + " elements; now contains " - + sizeOfResultingList + " elements")); - return filtered; - } - - @Override - public Iterator iterator() { - return new ArrayFiltererIterator(); - } - - @Override - public void remove(T object) { - this.removeList.add(object); - } - - /** - * Iterator for {@link ArrayFilterer} elements. - */ - private class ArrayFiltererIterator implements Iterator { - - private int index = 0; - - @Override - public boolean hasNext() { - return this.index < ArrayFilterer.this.list.length; - } - - @Override - public T next() { - if (hasNext()) { - return ArrayFilterer.this.list[this.index++]; - } - throw new NoSuchElementException(); - } - - } - -} diff --git a/access/src/main/java/org/springframework/security/acls/afterinvocation/CollectionFilterer.java b/access/src/main/java/org/springframework/security/acls/afterinvocation/CollectionFilterer.java deleted file mode 100644 index 99c75836471..00000000000 --- a/access/src/main/java/org/springframework/security/acls/afterinvocation/CollectionFilterer.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.afterinvocation; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; - -/** - * A filter used to filter Collections. - * - * @author Ben Alex - * @author Paulo Neves - * @deprecated please see {@code PostFilter} - */ -@Deprecated -class CollectionFilterer implements Filterer { - - protected static final Log logger = LogFactory.getLog(CollectionFilterer.class); - - private final Collection collection; - - private final Set removeList; - - CollectionFilterer(Collection collection) { - this.collection = collection; - // We create a Set of objects to be removed from the Collection, - // as ConcurrentModificationException prevents removal during - // iteration, and making a new Collection to be returned is - // problematic as the original Collection implementation passed - // to the method may not necessarily be re-constructable (as - // the Collection(collection) constructor is not guaranteed and - // manually adding may lose sort order or other capabilities) - this.removeList = new HashSet<>(); - } - - @Override - public Object getFilteredObject() { - // Now the Iterator has ended, remove Objects from Collection - Iterator removeIter = this.removeList.iterator(); - int originalSize = this.collection.size(); - while (removeIter.hasNext()) { - this.collection.remove(removeIter.next()); - } - logger.debug(LogMessage.of(() -> "Original collection contained " + originalSize + " elements; now contains " - + this.collection.size() + " elements")); - return this.collection; - } - - @Override - public Iterator iterator() { - return this.collection.iterator(); - } - - @Override - public void remove(T object) { - this.removeList.add(object); - } - -} diff --git a/access/src/main/java/org/springframework/security/acls/afterinvocation/Filterer.java b/access/src/main/java/org/springframework/security/acls/afterinvocation/Filterer.java deleted file mode 100644 index 953f6109e79..00000000000 --- a/access/src/main/java/org/springframework/security/acls/afterinvocation/Filterer.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.afterinvocation; - -import java.util.Iterator; - -/** - * Filterer strategy interface. - * - * @author Ben Alex - * @author Paulo Neves - * @deprecated please use {@code PreFilter} and {@code @PostFilter} instead - */ -@Deprecated -interface Filterer extends Iterable { - - /** - * Gets the filtered collection or array. - * @return the filtered collection or array - */ - Object getFilteredObject(); - - /** - * Returns an iterator over the filtered collection or array. - * @return an Iterator - */ - @Override - Iterator iterator(); - - /** - * Removes the given object from the resulting list. - * @param object the object to be removed - */ - void remove(T object); - -} diff --git a/access/src/main/java/org/springframework/security/acls/afterinvocation/package-info.java b/access/src/main/java/org/springframework/security/acls/afterinvocation/package-info.java deleted file mode 100644 index 82a6c15d6bd..00000000000 --- a/access/src/main/java/org/springframework/security/acls/afterinvocation/package-info.java +++ /dev/null @@ -1,21 +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. - */ - -/** - * After-invocation providers for collection and array filtering. Consider using a - * {@code PostFilter} annotation in preference. - */ -package org.springframework.security.acls.afterinvocation; diff --git a/access/src/main/java/org/springframework/security/messaging/access/expression/EvaluationContextPostProcessor.java b/access/src/main/java/org/springframework/security/messaging/access/expression/EvaluationContextPostProcessor.java deleted file mode 100644 index cadca0aab1d..00000000000 --- a/access/src/main/java/org/springframework/security/messaging/access/expression/EvaluationContextPostProcessor.java +++ /dev/null @@ -1,46 +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.messaging.access.expression; - -import org.springframework.expression.EvaluationContext; - -/** - * Allows post processing the {@link EvaluationContext} - * - *

- * This API is intentionally kept package scope as it may evolve over time. - *

- * - * @author Daniel Bustamante Ospina - * @since 5.2 - * @deprecated Since {@link MessageExpressionVoter} is deprecated, there is no more need - * for this class - */ -@Deprecated -interface EvaluationContextPostProcessor { - - /** - * Allows post processing of the {@link EvaluationContext}. Implementations may return - * a new instance of {@link EvaluationContext} or modify the {@link EvaluationContext} - * that was passed in. - * @param context the original {@link EvaluationContext} - * @param invocation the security invocation object (i.e. Message) - * @return the upated context. - */ - EvaluationContext postProcess(EvaluationContext context, I invocation); - -} diff --git a/access/src/main/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactory.java b/access/src/main/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactory.java deleted file mode 100644 index 9fa649fc360..00000000000 --- a/access/src/main/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactory.java +++ /dev/null @@ -1,129 +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.messaging.access.expression; - -import java.util.Arrays; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.springframework.expression.Expression; -import org.springframework.messaging.Message; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.expression.SecurityExpressionHandler; -import org.springframework.security.messaging.access.intercept.DefaultMessageSecurityMetadataSource; -import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource; -import org.springframework.security.messaging.util.matcher.MessageMatcher; - -/** - * A class used to create a {@link MessageSecurityMetadataSource} that uses - * {@link MessageMatcher} mapped to Spring Expressions. - * - * @author Rob Winch - * @since 4.0 - * @deprecated Use - * {@link org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager} - * instead - */ -@Deprecated -public final class ExpressionBasedMessageSecurityMetadataSourceFactory { - - private ExpressionBasedMessageSecurityMetadataSourceFactory() { - } - - /** - * Create a {@link MessageSecurityMetadataSource} that uses {@link MessageMatcher} - * mapped to Spring Expressions. Each entry is considered in order and only the first - * match is used. - * - * For example: - * - *
-	 *     LinkedHashMap<MessageMatcher<?>,String> matcherToExpression = new LinkedHashMap<MessageMatcher<Object>,String>();
-	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/public/**"), "permitAll");
-	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/admin/**"), "hasRole('ROLE_ADMIN')");
-	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/topics/{name}/**"), "@someBean.customLogic(authentication, #name)");
-	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/**"), "authenticated");
-	 *
-	 *     MessageSecurityMetadataSource metadataSource = createExpressionMessageMetadataSource(matcherToExpression);
-	 * 
- * - *

- * If our destination is "/public/hello", it would match on "/public/**" and on "/**". - * However, only "/public/**" would be used since it is the first entry. That means - * that a destination of "/public/hello" will be mapped to "permitAll". - * - *

- * For a complete listing of expressions see {@link MessageSecurityExpressionRoot} - * @param matcherToExpression an ordered mapping of {@link MessageMatcher} to Strings - * that are turned into an Expression using - * {@link DefaultMessageSecurityExpressionHandler#getExpressionParser()} - * @return the {@link MessageSecurityMetadataSource} to use. Cannot be null. - */ - public static MessageSecurityMetadataSource createExpressionMessageMetadataSource( - LinkedHashMap, String> matcherToExpression) { - return createExpressionMessageMetadataSource(matcherToExpression, - new DefaultMessageSecurityExpressionHandler<>()); - } - - /** - * Create a {@link MessageSecurityMetadataSource} that uses {@link MessageMatcher} - * mapped to Spring Expressions. Each entry is considered in order and only the first - * match is used. - * - * For example: - * - *

-	 *     LinkedHashMap<MessageMatcher<?>,String> matcherToExpression = new LinkedHashMap<MessageMatcher<Object>,String>();
-	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/public/**"), "permitAll");
-	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/admin/**"), "hasRole('ROLE_ADMIN')");
-	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/topics/{name}/**"), "@someBean.customLogic(authentication, #name)");
-	 *     matcherToExpression.put(new SimDestinationMessageMatcher("/**"), "authenticated");
-	 *
-	 *     MessageSecurityMetadataSource metadataSource = createExpressionMessageMetadataSource(matcherToExpression);
-	 * 
- * - *

- * If our destination is "/public/hello", it would match on "/public/**" and on "/**". - * However, only "/public/**" would be used since it is the first entry. That means - * that a destination of "/public/hello" will be mapped to "permitAll". - *

- * - *

- * For a complete listing of expressions see {@link MessageSecurityExpressionRoot} - *

- * @param matcherToExpression an ordered mapping of {@link MessageMatcher} to Strings - * that are turned into an Expression using - * {@link DefaultMessageSecurityExpressionHandler#getExpressionParser()} - * @param handler the {@link SecurityExpressionHandler} to use - * @return the {@link MessageSecurityMetadataSource} to use. Cannot be null. - */ - public static MessageSecurityMetadataSource createExpressionMessageMetadataSource( - LinkedHashMap, String> matcherToExpression, - SecurityExpressionHandler> handler) { - LinkedHashMap, Collection> matcherToAttrs = new LinkedHashMap<>(); - for (Map.Entry, String> entry : matcherToExpression.entrySet()) { - MessageMatcher matcher = entry.getKey(); - String rawExpression = entry.getValue(); - Expression expression = handler.getExpressionParser().parseExpression(rawExpression); - ConfigAttribute attribute = new MessageExpressionConfigAttribute(expression, matcher); - matcherToAttrs.put(matcher, Arrays.asList(attribute)); - } - return new DefaultMessageSecurityMetadataSource(matcherToAttrs); - } - -} diff --git a/access/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttribute.java b/access/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttribute.java deleted file mode 100644 index 61ae0481f3e..00000000000 --- a/access/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttribute.java +++ /dev/null @@ -1,83 +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.messaging.access.expression; - -import java.util.Map; - -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.messaging.Message; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.messaging.util.matcher.MessageMatcher; -import org.springframework.util.Assert; - -/** - * Simple expression configuration attribute for use in {@link Message} authorizations. - * - * @author Rob Winch - * @author Daniel Bustamante Ospina - * @since 4.0 - * @deprecated Use - * {@link org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager} - * instead - */ -@Deprecated -@SuppressWarnings("serial") -class MessageExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor> { - - private final Expression authorizeExpression; - - private final MessageMatcher matcher; - - /** - * Creates a new instance - * @param authorizeExpression the {@link Expression} to use. Cannot be null - * @param matcher the {@link MessageMatcher} used to match the messages. - */ - MessageExpressionConfigAttribute(Expression authorizeExpression, MessageMatcher matcher) { - Assert.notNull(authorizeExpression, "authorizeExpression cannot be null"); - Assert.notNull(matcher, "matcher cannot be null"); - this.authorizeExpression = authorizeExpression; - this.matcher = (MessageMatcher) matcher; - } - - Expression getAuthorizeExpression() { - return this.authorizeExpression; - } - - @Override - public @Nullable String getAttribute() { - return null; - } - - @Override - public String toString() { - return this.authorizeExpression.getExpressionString(); - } - - @Override - public EvaluationContext postProcess(EvaluationContext ctx, Message message) { - Map variables = this.matcher.matcher(message).getVariables(); - for (Map.Entry entry : variables.entrySet()) { - ctx.setVariable(entry.getKey(), entry.getValue()); - } - return ctx; - } - -} diff --git a/access/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionVoter.java b/access/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionVoter.java deleted file mode 100644 index c4866cab3ca..00000000000 --- a/access/src/main/java/org/springframework/security/messaging/access/expression/MessageExpressionVoter.java +++ /dev/null @@ -1,89 +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.messaging.access.expression; - -import java.util.Collection; - -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.EvaluationContext; -import org.springframework.messaging.Message; -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.expression.ExpressionUtils; -import org.springframework.security.access.expression.SecurityExpressionHandler; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * Voter which handles {@link Message} authorisation decisions. If a - * {@link MessageExpressionConfigAttribute} is found, then its expression is evaluated. If - * true, {@code ACCESS_GRANTED} is returned. If false, {@code ACCESS_DENIED} is returned. - * If no {@code MessageExpressionConfigAttribute} is found, then {@code ACCESS_ABSTAIN} is - * returned. - * - * @author Rob Winch - * @author Daniel Bustamante Ospina - * @since 4.0 - * @deprecated Use - * {@link org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager} - * instead - */ -@Deprecated -public class MessageExpressionVoter implements AccessDecisionVoter> { - - private SecurityExpressionHandler> expressionHandler = new DefaultMessageSecurityExpressionHandler<>(); - - @Override - public int vote(Authentication authentication, Message message, Collection attributes) { - Assert.notNull(authentication, "authentication must not be null"); - Assert.notNull(message, "message must not be null"); - Assert.notNull(attributes, "attributes must not be null"); - MessageExpressionConfigAttribute attr = findConfigAttribute(attributes); - if (attr == null) { - return ACCESS_ABSTAIN; - } - EvaluationContext ctx = this.expressionHandler.createEvaluationContext(authentication, message); - ctx = attr.postProcess(ctx, message); - return ExpressionUtils.evaluateAsBoolean(attr.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED : ACCESS_DENIED; - } - - private @Nullable MessageExpressionConfigAttribute findConfigAttribute(Collection attributes) { - for (ConfigAttribute attribute : attributes) { - if (attribute instanceof MessageExpressionConfigAttribute) { - return (MessageExpressionConfigAttribute) attribute; - } - } - return null; - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return attribute instanceof MessageExpressionConfigAttribute; - } - - @Override - public boolean supports(Class clazz) { - return Message.class.isAssignableFrom(clazz); - } - - public void setExpressionHandler(SecurityExpressionHandler> expressionHandler) { - Assert.notNull(expressionHandler, "expressionHandler cannot be null"); - this.expressionHandler = expressionHandler; - } - -} diff --git a/access/src/main/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptor.java b/access/src/main/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptor.java deleted file mode 100644 index 9e6fc2aae60..00000000000 --- a/access/src/main/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptor.java +++ /dev/null @@ -1,113 +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.messaging.access.intercept; - -import org.jspecify.annotations.Nullable; - -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.security.access.SecurityMetadataSource; -import org.springframework.security.access.intercept.AbstractSecurityInterceptor; -import org.springframework.security.access.intercept.InterceptorStatusToken; -import org.springframework.security.messaging.access.expression.ExpressionBasedMessageSecurityMetadataSourceFactory; -import org.springframework.util.Assert; - -/** - * Performs security handling of Message resources via a ChannelInterceptor - * implementation. - *

- * The SecurityMetadataSource required by this security interceptor is of - * type {@link MessageSecurityMetadataSource}. - *

- * Refer to {@link AbstractSecurityInterceptor} for details on the workflow. - * - * @author Rob Winch - * @since 4.0 - * @deprecated Use {@code AuthorizationChannelInterceptor} instead - */ -@Deprecated -public final class ChannelSecurityInterceptor extends AbstractSecurityInterceptor implements ChannelInterceptor { - - private static final ThreadLocal tokenHolder = new ThreadLocal<>(); - - private final MessageSecurityMetadataSource metadataSource; - - /** - * Creates a new instance - * @param metadataSource the MessageSecurityMetadataSource to use. Cannot be null. - * - * @see DefaultMessageSecurityMetadataSource - * @see ExpressionBasedMessageSecurityMetadataSourceFactory - */ - public ChannelSecurityInterceptor(MessageSecurityMetadataSource metadataSource) { - Assert.notNull(metadataSource, "metadataSource cannot be null"); - this.metadataSource = metadataSource; - } - - @Override - public Class getSecureObjectClass() { - return Message.class; - } - - @Override - public SecurityMetadataSource obtainSecurityMetadataSource() { - return this.metadataSource; - } - - @Override - public Message preSend(Message message, MessageChannel channel) { - InterceptorStatusToken token = beforeInvocation(message); - if (token != null) { - tokenHolder.set(token); - } - return message; - } - - @Override - public void postSend(Message message, MessageChannel channel, boolean sent) { - InterceptorStatusToken token = clearToken(); - afterInvocation(token, null); - } - - @Override - public void afterSendCompletion(Message message, MessageChannel channel, boolean sent, @Nullable Exception ex) { - InterceptorStatusToken token = clearToken(); - finallyInvocation(token); - } - - @Override - public boolean preReceive(MessageChannel channel) { - return true; - } - - @Override - public Message postReceive(Message message, MessageChannel channel) { - return message; - } - - @Override - public void afterReceiveCompletion(@Nullable Message message, MessageChannel channel, @Nullable Exception ex) { - } - - private InterceptorStatusToken clearToken() { - InterceptorStatusToken token = tokenHolder.get(); - tokenHolder.remove(); - return token; - } - -} diff --git a/access/src/main/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSource.java b/access/src/main/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSource.java deleted file mode 100644 index 7ab7315e631..00000000000 --- a/access/src/main/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSource.java +++ /dev/null @@ -1,82 +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.messaging.access.intercept; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -import org.springframework.messaging.Message; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.messaging.access.expression.ExpressionBasedMessageSecurityMetadataSourceFactory; -import org.springframework.security.messaging.util.matcher.MessageMatcher; - -/** - * A default implementation of {@link MessageSecurityMetadataSource} that looks up the - * {@link ConfigAttribute} instances using a {@link MessageMatcher}. - * - *

- * Each entry is considered in order. The first entry that matches, the corresponding - * {@code Collection} is returned. - *

- * - * @author Rob Winch - * @since 4.0 - * @see ChannelSecurityInterceptor - * @see ExpressionBasedMessageSecurityMetadataSourceFactory - * @deprecated Use {@link MessageMatcherDelegatingAuthorizationManager} instead - */ -@Deprecated -public final class DefaultMessageSecurityMetadataSource implements MessageSecurityMetadataSource { - - private final Map, Collection> messageMap; - - public DefaultMessageSecurityMetadataSource( - LinkedHashMap, Collection> messageMap) { - this.messageMap = messageMap; - } - - @Override - @SuppressWarnings({ "rawtypes", "unchecked" }) - public Collection getAttributes(Object object) throws IllegalArgumentException { - final Message message = (Message) object; - for (Map.Entry, Collection> entry : this.messageMap.entrySet()) { - if (entry.getKey().matches(message)) { - return entry.getValue(); - } - } - return Collections.emptyList(); - } - - @Override - public Collection getAllConfigAttributes() { - Set allAttributes = new HashSet<>(); - for (Collection entry : this.messageMap.values()) { - allAttributes.addAll(entry); - } - return allAttributes; - } - - @Override - public boolean supports(Class clazz) { - return Message.class.isAssignableFrom(clazz); - } - -} diff --git a/access/src/main/java/org/springframework/security/messaging/access/intercept/MessageSecurityMetadataSource.java b/access/src/main/java/org/springframework/security/messaging/access/intercept/MessageSecurityMetadataSource.java deleted file mode 100644 index 0187b7274b3..00000000000 --- a/access/src/main/java/org/springframework/security/messaging/access/intercept/MessageSecurityMetadataSource.java +++ /dev/null @@ -1,34 +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.messaging.access.intercept; - -import org.springframework.messaging.Message; -import org.springframework.security.access.SecurityMetadataSource; - -/** - * A {@link SecurityMetadataSource} that is used for securing {@link Message} - * - * @author Rob Winch - * @since 4.0 - * @see ChannelSecurityInterceptor - * @see DefaultMessageSecurityMetadataSource - * @deprecated Use {@link MessageMatcherDelegatingAuthorizationManager} instead - */ -@Deprecated -public interface MessageSecurityMetadataSource extends SecurityMetadataSource { - -} diff --git a/access/src/main/java/org/springframework/security/web/access/DefaultWebInvocationPrivilegeEvaluator.java b/access/src/main/java/org/springframework/security/web/access/DefaultWebInvocationPrivilegeEvaluator.java deleted file mode 100644 index 65bef09cf6e..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/DefaultWebInvocationPrivilegeEvaluator.java +++ /dev/null @@ -1,117 +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.web.access; - -import java.util.Collection; - -import jakarta.servlet.ServletContext; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.intercept.AbstractSecurityInterceptor; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.FilterInvocation; -import org.springframework.util.Assert; -import org.springframework.web.context.ServletContextAware; - -/** - * Allows users to determine whether they have privileges for a given web URI. - * - * @author Ben Alex - * @author Luke Taylor - * @since 3.0 - * @deprecated Use {@link AuthorizationManagerWebInvocationPrivilegeEvaluator} instead - */ -@Deprecated -public class DefaultWebInvocationPrivilegeEvaluator implements WebInvocationPrivilegeEvaluator, ServletContextAware { - - protected static final Log logger = LogFactory.getLog(DefaultWebInvocationPrivilegeEvaluator.class); - - private final AbstractSecurityInterceptor securityInterceptor; - - private @Nullable ServletContext servletContext; - - public DefaultWebInvocationPrivilegeEvaluator(AbstractSecurityInterceptor securityInterceptor) { - Assert.notNull(securityInterceptor, "SecurityInterceptor cannot be null"); - Assert.isTrue(FilterInvocation.class.equals(securityInterceptor.getSecureObjectClass()), - "AbstractSecurityInterceptor does not support FilterInvocations"); - Assert.notNull(securityInterceptor.getAccessDecisionManager(), - "AbstractSecurityInterceptor must provide a non-null AccessDecisionManager"); - this.securityInterceptor = securityInterceptor; - } - - /** - * Determines whether the user represented by the supplied Authentication - * object is allowed to invoke the supplied URI. - * @param uri the URI excluding the context path (a default context path setting will - * be used) - */ - @Override - public boolean isAllowed(String uri, @Nullable Authentication authentication) { - return isAllowed(null, uri, null, authentication); - } - - /** - * Determines whether the user represented by the supplied Authentication - * object is allowed to invoke the supplied URI, with the given . - *

- * Note the default implementation of FilterInvocationSecurityMetadataSource - * disregards the contextPath when evaluating which secure object - * metadata applies to a given request URI, so generally the contextPath - * is unimportant unless you are using a custom - * FilterInvocationSecurityMetadataSource. - * @param uri the URI excluding the context path - * @param contextPath the context path (may be null, in which case a default value - * will be used). - * @param method the HTTP method (or null, for any method) - * @param authentication the Authentication instance whose authorities should - * be used in evaluation whether access should be granted. - * @return true if access is allowed, false if denied - */ - @Override - public boolean isAllowed(@Nullable String contextPath, String uri, @Nullable String method, - @Nullable Authentication authentication) { - Assert.notNull(uri, "uri parameter is required"); - FilterInvocation filterInvocation = new FilterInvocation(contextPath, uri, method, this.servletContext); - Collection attributes = this.securityInterceptor.obtainSecurityMetadataSource() - .getAttributes(filterInvocation); - if (attributes == null) { - return (!this.securityInterceptor.isRejectPublicInvocations()); - } - if (authentication == null) { - return false; - } - try { - this.securityInterceptor.getAccessDecisionManager().decide(authentication, filterInvocation, attributes); - return true; - } - catch (AccessDeniedException ex) { - logger.debug(LogMessage.format("%s denied for %s", filterInvocation, authentication), ex); - return false; - } - } - - @Override - public void setServletContext(ServletContext servletContext) { - this.servletContext = servletContext; - } - -} diff --git a/access/src/main/java/org/springframework/security/web/access/channel/AbstractRetryEntryPoint.java b/access/src/main/java/org/springframework/security/web/access/channel/AbstractRetryEntryPoint.java deleted file mode 100644 index 73f8ea5d5a1..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/channel/AbstractRetryEntryPoint.java +++ /dev/null @@ -1,104 +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.web.access.channel; - -import java.io.IOException; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.web.DefaultRedirectStrategy; -import org.springframework.security.web.PortMapper; -import org.springframework.security.web.PortMapperImpl; -import org.springframework.security.web.RedirectStrategy; -import org.springframework.util.Assert; - -/** - * @author Luke Taylor - * @deprecated please use - * {@link org.springframework.security.web.transport.HttpsRedirectFilter} and its - * associated {@link PortMapper} - */ -@Deprecated -public abstract class AbstractRetryEntryPoint implements ChannelEntryPoint { - - protected final Log logger = LogFactory.getLog(getClass()); - - private PortMapper portMapper = new PortMapperImpl(); - - /** - * The scheme ("http://" or "https://") - */ - private final String scheme; - - /** - * The standard port for the scheme (80 for http, 443 for https) - */ - private final int standardPort; - - private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); - - public AbstractRetryEntryPoint(String scheme, int standardPort) { - this.scheme = scheme; - this.standardPort = standardPort; - } - - @Override - public void commence(HttpServletRequest request, HttpServletResponse response) throws IOException { - String queryString = request.getQueryString(); - String redirectUrl = request.getRequestURI() + ((queryString != null) ? ("?" + queryString) : ""); - Integer currentPort = this.portMapper.getServerPort(request); - Integer redirectPort = getMappedPort(currentPort); - if (redirectPort != null) { - boolean includePort = redirectPort != this.standardPort; - String port = (includePort) ? (":" + redirectPort) : ""; - redirectUrl = this.scheme + request.getServerName() + port + redirectUrl; - } - this.logger.debug(LogMessage.format("Redirecting to: %s", redirectUrl)); - this.redirectStrategy.sendRedirect(request, response, redirectUrl); - } - - protected abstract @Nullable Integer getMappedPort(Integer mapFromPort); - - protected final PortMapper getPortMapper() { - return this.portMapper; - } - - public void setPortMapper(PortMapper portMapper) { - Assert.notNull(portMapper, "portMapper cannot be null"); - this.portMapper = portMapper; - } - - /** - * Sets the strategy to be used for redirecting to the required channel URL. A - * {@code DefaultRedirectStrategy} instance will be used if not set. - * @param redirectStrategy the strategy instance to which the URL will be passed. - */ - public void setRedirectStrategy(RedirectStrategy redirectStrategy) { - Assert.notNull(redirectStrategy, "redirectStrategy cannot be null"); - this.redirectStrategy = redirectStrategy; - } - - protected final RedirectStrategy getRedirectStrategy() { - return this.redirectStrategy; - } - -} diff --git a/access/src/main/java/org/springframework/security/web/access/channel/ChannelDecisionManager.java b/access/src/main/java/org/springframework/security/web/access/channel/ChannelDecisionManager.java deleted file mode 100644 index 71f3091d4e1..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/channel/ChannelDecisionManager.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.channel; - -import java.io.IOException; -import java.util.Collection; - -import jakarta.servlet.ServletException; - -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.util.matcher.RequestMatcher; - -/** - * Decides whether a web channel provides sufficient security. - * - * @author Ben Alex - * @deprecated no replacement is planned, though consider using a custom - * {@link RequestMatcher} for any sophisticated decision-making - */ -@Deprecated -public interface ChannelDecisionManager { - - /** - * Decided whether the presented {@link FilterInvocation} provides the appropriate - * level of channel security based on the requested list of ConfigAttributes. - * - */ - void decide(FilterInvocation invocation, Collection config) throws IOException, ServletException; - - /** - * Indicates whether this ChannelDecisionManager is able to process the - * passed ConfigAttribute. - *

- * This allows the ChannelProcessingFilter to check every configuration - * attribute can be consumed by the configured ChannelDecisionManager. - *

- * @param attribute a configuration attribute that has been configured against the - * ChannelProcessingFilter - * @return true if this ChannelDecisionManager can support the passed - * configuration attribute - */ - boolean supports(ConfigAttribute attribute); - -} diff --git a/access/src/main/java/org/springframework/security/web/access/channel/ChannelDecisionManagerImpl.java b/access/src/main/java/org/springframework/security/web/access/channel/ChannelDecisionManagerImpl.java deleted file mode 100644 index e82019f5323..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/channel/ChannelDecisionManagerImpl.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.channel; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import jakarta.servlet.ServletException; -import org.jspecify.annotations.NullUnmarked; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * Implementation of {@link ChannelDecisionManager}. - *

- * Iterates through each configured {@link ChannelProcessor}. If a - * ChannelProcessor has any issue with the security of the request, it should - * cause a redirect, exception or whatever other action is appropriate for the - * ChannelProcessor implementation. - *

- * Once any response is committed (ie a redirect is written to the response object), the - * ChannelDecisionManagerImpl will not iterate through any further - * ChannelProcessors. - *

- * The attribute "ANY_CHANNEL" if applied to a particular URL, the iteration through the - * channel processors will be skipped (see SEC-494, SEC-335). - * - * @author Ben Alex - * @deprecated no replacement is planned, though consider using a custom - * {@link RequestMatcher} for any sophisticated decision-making - */ -@Deprecated -@NullUnmarked -public class ChannelDecisionManagerImpl implements ChannelDecisionManager, InitializingBean { - - public static final String ANY_CHANNEL = "ANY_CHANNEL"; - - private List channelProcessors; - - @Override - public void afterPropertiesSet() { - Assert.notEmpty(this.channelProcessors, "A list of ChannelProcessors is required"); - } - - @Override - public void decide(FilterInvocation invocation, Collection config) - throws IOException, ServletException { - for (ConfigAttribute attribute : config) { - if (ANY_CHANNEL.equals(attribute.getAttribute())) { - return; - } - } - for (ChannelProcessor processor : this.channelProcessors) { - processor.decide(invocation, config); - if (invocation.getResponse().isCommitted()) { - break; - } - } - } - - protected @Nullable List getChannelProcessors() { - return this.channelProcessors; - } - - @SuppressWarnings("cast") - public void setChannelProcessors(List channelProcessors) { - Assert.notEmpty(channelProcessors, "A list of ChannelProcessors is required"); - this.channelProcessors = new ArrayList<>(channelProcessors.size()); - for (Object currentObject : channelProcessors) { - Assert.isInstanceOf(ChannelProcessor.class, currentObject, () -> "ChannelProcessor " - + currentObject.getClass().getName() + " must implement ChannelProcessor"); - this.channelProcessors.add((ChannelProcessor) currentObject); - } - } - - @Override - public boolean supports(ConfigAttribute attribute) { - if (ANY_CHANNEL.equals(attribute.getAttribute())) { - return true; - } - for (ChannelProcessor processor : this.channelProcessors) { - if (processor.supports(attribute)) { - return true; - } - } - return false; - } - -} diff --git a/access/src/main/java/org/springframework/security/web/access/channel/ChannelEntryPoint.java b/access/src/main/java/org/springframework/security/web/access/channel/ChannelEntryPoint.java deleted file mode 100644 index 405b861179f..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/channel/ChannelEntryPoint.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.channel; - -import java.io.IOException; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.security.web.PortMapper; - -/** - * May be used by a {@link ChannelProcessor} to launch a web channel. - * - *

- * ChannelProcessors can elect to launch a new web channel directly, or they - * can delegate to another class. The ChannelEntryPoint is a pluggable - * interface to assist ChannelProcessors in performing this delegation. - * - * @author Ben Alex - * @deprecated please use - * {@link org.springframework.security.web.transport.HttpsRedirectFilter} and its - * associated {@link PortMapper} - */ -@Deprecated -public interface ChannelEntryPoint { - - /** - * Commences a secure channel. - *

- * Implementations should modify the headers on the ServletResponse as - * necessary to commence the user agent using the implementation's supported channel - * type. - * @param request that a ChannelProcessor has rejected - * @param response so that the user agent can begin using a new channel - * - */ - void commence(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException; - -} diff --git a/access/src/main/java/org/springframework/security/web/access/channel/ChannelProcessingFilter.java b/access/src/main/java/org/springframework/security/web/access/channel/ChannelProcessingFilter.java deleted file mode 100644 index ee2982fc64c..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/channel/ChannelProcessingFilter.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.channel; - -import java.io.IOException; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.jspecify.annotations.Nullable; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; -import org.springframework.util.Assert; -import org.springframework.web.filter.GenericFilterBean; - -/** - * Ensures a web request is delivered over the required channel. - *

- * Internally uses a {@link FilterInvocation} to represent the request, allowing a - * {@code FilterInvocationSecurityMetadataSource} to be used to lookup the attributes - * which apply. - *

- * Delegates the actual channel security decisions and necessary actions to the configured - * {@link ChannelDecisionManager}. If a response is committed by the - * {@code ChannelDecisionManager}, the filter chain will not proceed. - *

- * The most common usage is to ensure that a request takes place over HTTPS, where the - * {@link ChannelDecisionManagerImpl} is configured with a {@link SecureChannelProcessor} - * and an {@link InsecureChannelProcessor}. A typical configuration would be - * - *

- *
- * <bean id="channelProcessingFilter" class="org.springframework.security.web.access.channel.ChannelProcessingFilter">
- *   <property name="channelDecisionManager" ref="channelDecisionManager"/>
- *   <property name="securityMetadataSource">
- *     <security:filter-security-metadata-source request-matcher="regex">
- *       <security:intercept-url pattern="\A/secure/.*\Z" access="REQUIRES_SECURE_CHANNEL"/>
- *       <security:intercept-url pattern="\A/login.jsp.*\Z" access="REQUIRES_SECURE_CHANNEL"/>
- *       <security:intercept-url pattern="\A/.*\Z" access="ANY_CHANNEL"/>
- *     </security:filter-security-metadata-source>
- *   </property>
- * </bean>
- *
- * <bean id="channelDecisionManager" class="org.springframework.security.web.access.channel.ChannelDecisionManagerImpl">
- *   <property name="channelProcessors">
- *     <list>
- *     <ref bean="secureChannelProcessor"/>
- *     <ref bean="insecureChannelProcessor"/>
- *     </list>
- *   </property>
- * </bean>
- *
- * <bean id="secureChannelProcessor"
- *   class="org.springframework.security.web.access.channel.SecureChannelProcessor"/>
- * <bean id="insecureChannelProcessor"
- *   class="org.springframework.security.web.access.channel.InsecureChannelProcessor"/>
- *
- * 
- * - * which would force the login form and any access to the {@code /secure} path to be made - * over HTTPS. - * - * @author Ben Alex - * @deprecated see {@link org.springframework.security.web.transport.HttpsRedirectFilter} - */ -@Deprecated -public class ChannelProcessingFilter extends GenericFilterBean { - - @SuppressWarnings("NullAway.Init") - private ChannelDecisionManager channelDecisionManager; - - @SuppressWarnings("NullAway.Init") - private FilterInvocationSecurityMetadataSource securityMetadataSource; - - @Override - public void afterPropertiesSet() { - Assert.notNull(this.securityMetadataSource, "securityMetadataSource must be specified"); - Assert.notNull(this.channelDecisionManager, "channelDecisionManager must be specified"); - Collection attributes = this.securityMetadataSource.getAllConfigAttributes(); - if (attributes == null) { - this.logger.warn("Could not validate configuration attributes as the " - + "FilterInvocationSecurityMetadataSource did not return any attributes"); - return; - } - Set unsupportedAttributes = getUnsupportedAttributes(attributes); - Assert.isTrue(unsupportedAttributes.isEmpty(), - () -> "Unsupported configuration attributes: " + unsupportedAttributes); - this.logger.info("Validated configuration attributes"); - } - - private Set getUnsupportedAttributes(Collection attrDefs) { - Set unsupportedAttributes = new HashSet<>(); - for (ConfigAttribute attr : attrDefs) { - if (!this.channelDecisionManager.supports(attr)) { - unsupportedAttributes.add(attr); - } - } - return unsupportedAttributes; - } - - @Override - public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) - throws IOException, ServletException { - HttpServletRequest request = (HttpServletRequest) req; - HttpServletResponse response = (HttpServletResponse) res; - FilterInvocation filterInvocation = new FilterInvocation(request, response, chain); - Collection attributes = this.securityMetadataSource.getAttributes(filterInvocation); - if (attributes != null) { - this.logger.debug(LogMessage.format("Request: %s; ConfigAttributes: %s", filterInvocation, attributes)); - this.channelDecisionManager.decide(filterInvocation, attributes); - @Nullable HttpServletResponse channelResponse = filterInvocation.getResponse(); - Assert.notNull(channelResponse, "HttpServletResponse is required"); - if (channelResponse.isCommitted()) { - return; - } - } - chain.doFilter(request, response); - } - - protected @Nullable ChannelDecisionManager getChannelDecisionManager() { - return this.channelDecisionManager; - } - - protected FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { - return this.securityMetadataSource; - } - - public void setChannelDecisionManager(ChannelDecisionManager channelDecisionManager) { - this.channelDecisionManager = channelDecisionManager; - } - - public void setSecurityMetadataSource( - FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource) { - this.securityMetadataSource = filterInvocationSecurityMetadataSource; - } - -} diff --git a/access/src/main/java/org/springframework/security/web/access/channel/ChannelProcessor.java b/access/src/main/java/org/springframework/security/web/access/channel/ChannelProcessor.java deleted file mode 100644 index 56ab0f262be..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/channel/ChannelProcessor.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.channel; - -import java.io.IOException; -import java.util.Collection; - -import jakarta.servlet.ServletException; - -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.util.matcher.RequestMatcher; - -/** - * Decides whether a web channel meets a specific security condition. - *

- * ChannelProcessor implementations are iterated by the - * {@link ChannelDecisionManagerImpl}. - *

- * If an implementation has an issue with the channel security, they should take action - * themselves. The callers of the implementation do not take any action. - * - * @author Ben Alex - * @deprecated no replacement is planned, though consider using a custom - * {@link RequestMatcher} for any sophisticated decision-making - */ -@Deprecated -public interface ChannelProcessor { - - /** - * Decided whether the presented {@link FilterInvocation} provides the appropriate - * level of channel security based on the requested list of ConfigAttributes. - */ - void decide(FilterInvocation invocation, Collection config) throws IOException, ServletException; - - /** - * Indicates whether this ChannelProcessor is able to process the passed - * ConfigAttribute. - *

- * This allows the ChannelProcessingFilter to check every configuration - * attribute can be consumed by the configured ChannelDecisionManager. - * @param attribute a configuration attribute that has been configured against the - * ChannelProcessingFilter. - * @return true if this ChannelProcessor can support the passed - * configuration attribute - */ - boolean supports(ConfigAttribute attribute); - -} diff --git a/access/src/main/java/org/springframework/security/web/access/channel/InsecureChannelProcessor.java b/access/src/main/java/org/springframework/security/web/access/channel/InsecureChannelProcessor.java deleted file mode 100644 index ba85920c48e..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/channel/InsecureChannelProcessor.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.channel; - -import java.io.IOException; -import java.util.Collection; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletResponse; -import org.jspecify.annotations.Nullable; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * Ensures channel security is inactive by review of - * HttpServletRequest.isSecure() responses. - *

- * The class responds to one case-sensitive keyword, {@link #getInsecureKeyword}. If this - * keyword is detected, HttpServletRequest.isSecure() is used to determine - * the channel security offered. If channel security is present, the configured - * ChannelEntryPoint is called. By default the entry point is - * {@link RetryWithHttpEntryPoint}. - *

- * The default insecureKeyword is REQUIRES_INSECURE_CHANNEL. - * - * @author Ben Alex - * @deprecated no replacement is planned, though consider using a custom - * {@link RequestMatcher} for any sophisticated decision-making - */ -@Deprecated -public class InsecureChannelProcessor implements InitializingBean, ChannelProcessor { - - private ChannelEntryPoint entryPoint = new RetryWithHttpEntryPoint(); - - private String insecureKeyword = "REQUIRES_INSECURE_CHANNEL"; - - @Override - public void afterPropertiesSet() { - Assert.hasLength(this.insecureKeyword, "insecureKeyword required"); - Assert.notNull(this.entryPoint, "entryPoint required"); - } - - @Override - public void decide(FilterInvocation invocation, Collection config) - throws IOException, ServletException { - Assert.isTrue(invocation != null && config != null, "Nulls cannot be provided"); - for (ConfigAttribute attribute : config) { - if (supports(attribute)) { - if (invocation.getHttpRequest().isSecure()) { - @Nullable HttpServletResponse response = invocation.getResponse(); - Assert.notNull(response, "HttpServletResponse required"); - this.entryPoint.commence(invocation.getRequest(), response); - } - } - } - } - - public ChannelEntryPoint getEntryPoint() { - return this.entryPoint; - } - - public String getInsecureKeyword() { - return this.insecureKeyword; - } - - public void setEntryPoint(ChannelEntryPoint entryPoint) { - this.entryPoint = entryPoint; - } - - public void setInsecureKeyword(String secureKeyword) { - this.insecureKeyword = secureKeyword; - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return (attribute != null) && (attribute.getAttribute() != null) - && attribute.getAttribute().equals(getInsecureKeyword()); - } - -} diff --git a/access/src/main/java/org/springframework/security/web/access/channel/RetryWithHttpEntryPoint.java b/access/src/main/java/org/springframework/security/web/access/channel/RetryWithHttpEntryPoint.java deleted file mode 100644 index 72c5cb01bab..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/channel/RetryWithHttpEntryPoint.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.channel; - -import org.jspecify.annotations.Nullable; - -import org.springframework.security.web.PortMapper; - -/** - * Commences an insecure channel by retrying the original request using HTTP. - *

- * This entry point should suffice in most circumstances. However, it is not intended to - * properly handle HTTP POSTs or other usage where a standard redirect would cause an - * issue. - * - * @author Ben Alex - * @deprecated please use - * {@link org.springframework.security.web.transport.HttpsRedirectFilter} and its - * associated {@link PortMapper} - */ -@Deprecated(since = "6.5") -public class RetryWithHttpEntryPoint extends AbstractRetryEntryPoint { - - public RetryWithHttpEntryPoint() { - super("http://", 80); - } - - @Override - protected @Nullable Integer getMappedPort(Integer mapFromPort) { - return getPortMapper().lookupHttpPort(mapFromPort); - } - -} diff --git a/access/src/main/java/org/springframework/security/web/access/channel/RetryWithHttpsEntryPoint.java b/access/src/main/java/org/springframework/security/web/access/channel/RetryWithHttpsEntryPoint.java deleted file mode 100644 index 25b87ed6b41..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/channel/RetryWithHttpsEntryPoint.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.channel; - -import org.jspecify.annotations.Nullable; - -import org.springframework.security.web.PortMapper; - -/** - * Commences a secure channel by retrying the original request using HTTPS. - *

- * This entry point should suffice in most circumstances. However, it is not intended to - * properly handle HTTP POSTs or other usage where a standard redirect would cause an - * issue. - *

- * - * @author Ben Alex - * @deprecated please use - * {@link org.springframework.security.web.transport.HttpsRedirectFilter} and its - * associated {@link PortMapper} - */ -@Deprecated(since = "6.5") -public class RetryWithHttpsEntryPoint extends AbstractRetryEntryPoint { - - public RetryWithHttpsEntryPoint() { - super("https://", 443); - } - - @Override - protected @Nullable Integer getMappedPort(Integer mapFromPort) { - return getPortMapper().lookupHttpsPort(mapFromPort); - } - -} diff --git a/access/src/main/java/org/springframework/security/web/access/channel/SecureChannelProcessor.java b/access/src/main/java/org/springframework/security/web/access/channel/SecureChannelProcessor.java deleted file mode 100644 index 2a1f985dad7..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/channel/SecureChannelProcessor.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.channel; - -import java.io.IOException; -import java.util.Collection; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * Ensures channel security is active by review of - * HttpServletRequest.isSecure() responses. - *

- * The class responds to one case-sensitive keyword, {@link #getSecureKeyword}. If this - * keyword is detected, HttpServletRequest.isSecure() is used to determine - * the channel security offered. If channel security is not present, the configured - * ChannelEntryPoint is called. By default the entry point is - * {@link RetryWithHttpsEntryPoint}. - *

- * The default secureKeyword is REQUIRES_SECURE_CHANNEL. - * - * @author Ben Alex - * @deprecated no replacement is planned, though consider using a custom - * {@link RequestMatcher} for any sophisticated decision-making - */ -@Deprecated -public class SecureChannelProcessor implements InitializingBean, ChannelProcessor { - - private ChannelEntryPoint entryPoint = new RetryWithHttpsEntryPoint(); - - private String secureKeyword = "REQUIRES_SECURE_CHANNEL"; - - @Override - public void afterPropertiesSet() { - Assert.hasLength(this.secureKeyword, "secureKeyword required"); - Assert.notNull(this.entryPoint, "entryPoint required"); - } - - @Override - public void decide(FilterInvocation invocation, Collection config) - throws IOException, ServletException { - Assert.isTrue((invocation != null) && (config != null), "Nulls cannot be provided"); - for (ConfigAttribute attribute : config) { - if (supports(attribute)) { - if (!invocation.getHttpRequest().isSecure()) { - HttpServletResponse response = invocation.getResponse(); - Assert.notNull(response, "HttpServletResponse is required"); - this.entryPoint.commence(invocation.getRequest(), response); - } - } - } - } - - public ChannelEntryPoint getEntryPoint() { - return this.entryPoint; - } - - public String getSecureKeyword() { - return this.secureKeyword; - } - - public void setEntryPoint(ChannelEntryPoint entryPoint) { - this.entryPoint = entryPoint; - } - - public void setSecureKeyword(String secureKeyword) { - this.secureKeyword = secureKeyword; - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return (attribute != null) && (attribute.getAttribute() != null) - && attribute.getAttribute().equals(getSecureKeyword()); - } - -} diff --git a/access/src/main/java/org/springframework/security/web/access/channel/package-info.java b/access/src/main/java/org/springframework/security/web/access/channel/package-info.java deleted file mode 100644 index 353af41a71c..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/channel/package-info.java +++ /dev/null @@ -1,25 +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. - */ - -/** - * Classes that ensure web requests are received over required transport channels. - *

- * Most commonly used to enforce that requests are submitted over HTTP or HTTPS. - */ -@NullMarked -package org.springframework.security.web.access.channel; - -import org.jspecify.annotations.NullMarked; diff --git a/access/src/main/java/org/springframework/security/web/access/expression/DefaultWebSecurityExpressionHandler.java b/access/src/main/java/org/springframework/security/web/access/expression/DefaultWebSecurityExpressionHandler.java deleted file mode 100644 index 09bfe1a8dac..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/expression/DefaultWebSecurityExpressionHandler.java +++ /dev/null @@ -1,95 +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.web.access.expression; - -import org.jspecify.annotations.Nullable; - -import org.springframework.security.access.expression.AbstractSecurityExpressionHandler; -import org.springframework.security.access.expression.SecurityExpressionHandler; -import org.springframework.security.access.expression.SecurityExpressionOperations; -import org.springframework.security.authentication.AuthenticationTrustResolver; -import org.springframework.security.authentication.AuthenticationTrustResolverImpl; -import org.springframework.security.authorization.AuthorizationManagerFactory; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.FilterInvocation; - -/** - * @author Luke Taylor - * @author Eddú Meléndez - * @author Steve Riesenberg - * @since 3.0 - */ -public class DefaultWebSecurityExpressionHandler extends AbstractSecurityExpressionHandler - implements SecurityExpressionHandler { - - private static final String DEFAULT_ROLE_PREFIX = "ROLE_"; - - private String defaultRolePrefix = DEFAULT_ROLE_PREFIX; - - @Override - protected SecurityExpressionOperations createSecurityExpressionRoot(@Nullable Authentication authentication, - FilterInvocation fi) { - FilterInvocationExpressionRoot root = new FilterInvocationExpressionRoot(() -> authentication, fi); - root.setAuthorizationManagerFactory(getAuthorizationManagerFactory()); - root.setPermissionEvaluator(getPermissionEvaluator()); - if (!DEFAULT_ROLE_PREFIX.equals(this.defaultRolePrefix)) { - // Ensure SecurityExpressionRoot can strip the custom role prefix - root.setDefaultRolePrefix(this.defaultRolePrefix); - } - return root; - } - - /** - * Sets the {@link AuthenticationTrustResolver} to be used. The default is - * {@link AuthenticationTrustResolverImpl}. - * @param trustResolver the {@link AuthenticationTrustResolver} to use. Cannot be - * null. - * @deprecated Use - * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead - */ - @Deprecated(since = "7.0") - public void setTrustResolver(AuthenticationTrustResolver trustResolver) { - getDefaultAuthorizationManagerFactory().setTrustResolver(trustResolver); - } - - /** - *

- * Sets the default prefix to be added to - * {@link org.springframework.security.access.expression.SecurityExpressionRoot#hasAnyRole(String...)} - * or - * {@link org.springframework.security.access.expression.SecurityExpressionRoot#hasRole(String)}. - * For example, if hasRole("ADMIN") or hasRole("ROLE_ADMIN") is passed in, then the - * role ROLE_ADMIN will be used when the defaultRolePrefix is "ROLE_" (default). - *

- * - *

- * If null or empty, then no default role prefix is used. - *

- * @param defaultRolePrefix the default prefix to add to roles. Default "ROLE_". - * @deprecated Use - * {@link #setAuthorizationManagerFactory(AuthorizationManagerFactory)} instead - */ - @Deprecated(since = "7.0") - public void setDefaultRolePrefix(@Nullable String defaultRolePrefix) { - if (defaultRolePrefix == null) { - defaultRolePrefix = ""; - } - getDefaultAuthorizationManagerFactory().setRolePrefix(defaultRolePrefix); - this.defaultRolePrefix = defaultRolePrefix; - } - -} diff --git a/access/src/main/java/org/springframework/security/web/access/expression/ExpressionBasedFilterInvocationSecurityMetadataSource.java b/access/src/main/java/org/springframework/security/web/access/expression/ExpressionBasedFilterInvocationSecurityMetadataSource.java deleted file mode 100644 index 489b4d951e5..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/expression/ExpressionBasedFilterInvocationSecurityMetadataSource.java +++ /dev/null @@ -1,117 +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.web.access.expression; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.function.BiConsumer; - -import jakarta.servlet.http.HttpServletRequest; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.ParseException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.expression.SecurityExpressionHandler; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.annotation.SecurityAnnotationScanner; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.util.Assert; - -/** - * Expression-based {@code FilterInvocationSecurityMetadataSource}. - * - * @author Luke Taylor - * @author Eddú Meléndez - * @since 3.0 - * @deprecated In modern Spring Security APIs, each API manages its own configuration - * context. As such there is no direct replacement for this interface. In the case of - * method security, please see {@link SecurityAnnotationScanner} and - * {@link AuthorizationManager}. In the case of channel security, please see - * {@code HttpsRedirectFilter}. In the case of web security, please see - * {@link AuthorizationManager}. - */ -@Deprecated -public final class ExpressionBasedFilterInvocationSecurityMetadataSource - extends DefaultFilterInvocationSecurityMetadataSource { - - private static final Log logger = LogFactory.getLog(ExpressionBasedFilterInvocationSecurityMetadataSource.class); - - public ExpressionBasedFilterInvocationSecurityMetadataSource( - LinkedHashMap> requestMap, - SecurityExpressionHandler expressionHandler) { - super(processMap(requestMap, expressionHandler.getExpressionParser())); - Assert.notNull(expressionHandler, "A non-null SecurityExpressionHandler is required"); - } - - private static LinkedHashMap> processMap( - LinkedHashMap> requestMap, ExpressionParser parser) { - Assert.notNull(parser, "SecurityExpressionHandler returned a null parser object"); - LinkedHashMap> processed = new LinkedHashMap<>(requestMap); - requestMap.forEach((request, value) -> process(parser, request, value, processed::put)); - return processed; - } - - private static void process(ExpressionParser parser, RequestMatcher request, Collection value, - BiConsumer> consumer) { - String expression = getExpression(request, value); - if (logger.isDebugEnabled()) { - logger.debug(LogMessage.format("Adding web access control expression [%s] for %s", expression, request)); - } - AbstractVariableEvaluationContextPostProcessor postProcessor = createPostProcessor(request); - ArrayList processed = new ArrayList<>(1); - try { - processed.add(new WebExpressionConfigAttribute(parser.parseExpression(expression), postProcessor)); - } - catch (ParseException ex) { - throw new IllegalArgumentException("Failed to parse expression '" + expression + "'"); - } - consumer.accept(request, processed); - } - - private static String getExpression(RequestMatcher request, Collection value) { - Assert.isTrue(value.size() == 1, () -> "Expected a single expression attribute for " + request); - return value.toArray(new ConfigAttribute[1])[0].getAttribute(); - } - - private static AbstractVariableEvaluationContextPostProcessor createPostProcessor(RequestMatcher request) { - return new RequestVariablesExtractorEvaluationContextPostProcessor(request); - } - - static class RequestVariablesExtractorEvaluationContextPostProcessor - extends AbstractVariableEvaluationContextPostProcessor { - - private final RequestMatcher matcher; - - RequestVariablesExtractorEvaluationContextPostProcessor(RequestMatcher matcher) { - this.matcher = matcher; - } - - @Override - Map extractVariables(HttpServletRequest request) { - return this.matcher.matcher(request).getVariables(); - } - - } - -} diff --git a/access/src/main/java/org/springframework/security/web/access/expression/WebExpressionConfigAttribute.java b/access/src/main/java/org/springframework/security/web/access/expression/WebExpressionConfigAttribute.java deleted file mode 100644 index 6e3b140a514..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/expression/WebExpressionConfigAttribute.java +++ /dev/null @@ -1,69 +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.web.access.expression; - -import org.jspecify.annotations.NullUnmarked; - -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.web.FilterInvocation; - -/** - * Simple expression configuration attribute for use in web request authorizations. - * - * @author Luke Taylor - * @since 3.0 - * @deprecated In modern Spring Security APIs, each API manages its own configuration - * context. As such there is no direct replacement for this interface. Please see - * {@link AuthorizationManager}. - */ -@Deprecated -@NullUnmarked -class WebExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor { - - private final Expression authorizeExpression; - - private final EvaluationContextPostProcessor postProcessor; - - WebExpressionConfigAttribute(Expression authorizeExpression, - EvaluationContextPostProcessor postProcessor) { - this.authorizeExpression = authorizeExpression; - this.postProcessor = postProcessor; - } - - Expression getAuthorizeExpression() { - return this.authorizeExpression; - } - - @Override - public EvaluationContext postProcess(EvaluationContext context, FilterInvocation fi) { - return (this.postProcessor != null) ? this.postProcessor.postProcess(context, fi) : context; - } - - @Override - public String getAttribute() { - return null; - } - - @Override - public String toString() { - return this.authorizeExpression.getExpressionString(); - } - -} diff --git a/access/src/main/java/org/springframework/security/web/access/expression/WebExpressionVoter.java b/access/src/main/java/org/springframework/security/web/access/expression/WebExpressionVoter.java deleted file mode 100644 index 4405fe25b5f..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/expression/WebExpressionVoter.java +++ /dev/null @@ -1,93 +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.web.access.expression; - -import java.util.Collection; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jspecify.annotations.Nullable; - -import org.springframework.expression.EvaluationContext; -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.expression.ExpressionUtils; -import org.springframework.security.access.expression.SecurityExpressionHandler; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.FilterInvocation; -import org.springframework.util.Assert; - -/** - * Voter which handles web authorisation decisions. - * - * @author Luke Taylor - * @since 3.0 - * @deprecated Use {@link WebExpressionAuthorizationManager} instead - */ -@Deprecated -public class WebExpressionVoter implements AccessDecisionVoter { - - private final Log logger = LogFactory.getLog(getClass()); - - private SecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler(); - - @Override - public int vote(Authentication authentication, FilterInvocation filterInvocation, - Collection attributes) { - Assert.notNull(authentication, "authentication must not be null"); - Assert.notNull(filterInvocation, "filterInvocation must not be null"); - Assert.notNull(attributes, "attributes must not be null"); - WebExpressionConfigAttribute webExpressionConfigAttribute = findConfigAttribute(attributes); - if (webExpressionConfigAttribute == null) { - this.logger - .trace("Abstained since did not find a config attribute of instance WebExpressionConfigAttribute"); - return ACCESS_ABSTAIN; - } - EvaluationContext ctx = webExpressionConfigAttribute.postProcess( - this.expressionHandler.createEvaluationContext(authentication, filterInvocation), filterInvocation); - boolean granted = ExpressionUtils.evaluateAsBoolean(webExpressionConfigAttribute.getAuthorizeExpression(), ctx); - if (granted) { - return ACCESS_GRANTED; - } - this.logger.trace("Voted to deny authorization"); - return ACCESS_DENIED; - } - - private @Nullable WebExpressionConfigAttribute findConfigAttribute(Collection attributes) { - for (ConfigAttribute attribute : attributes) { - if (attribute instanceof WebExpressionConfigAttribute) { - return (WebExpressionConfigAttribute) attribute; - } - } - return null; - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return attribute instanceof WebExpressionConfigAttribute; - } - - @Override - public boolean supports(Class clazz) { - return FilterInvocation.class.isAssignableFrom(clazz); - } - - public void setExpressionHandler(SecurityExpressionHandler expressionHandler) { - this.expressionHandler = expressionHandler; - } - -} diff --git a/access/src/main/java/org/springframework/security/web/access/intercept/DefaultFilterInvocationSecurityMetadataSource.java b/access/src/main/java/org/springframework/security/web/access/intercept/DefaultFilterInvocationSecurityMetadataSource.java deleted file mode 100644 index 5802901dfe9..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/intercept/DefaultFilterInvocationSecurityMetadataSource.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.intercept; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -import jakarta.servlet.http.HttpServletRequest; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.annotation.SecurityAnnotationScanner; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.util.matcher.RequestMatcher; - -/** - * Default implementation of FilterInvocationDefinitionSource. - *

- * Stores an ordered map of {@link RequestMatcher}s to ConfigAttribute - * collections and provides matching of {@code FilterInvocation}s against the items stored - * in the map. - *

- * The order of the {@link RequestMatcher}s in the map is very important. The first - * one which matches the request will be used. Later matchers in the map will not be - * invoked if a match has already been found. Accordingly, the most specific matchers - * should be registered first, with the most general matches registered last. - *

- * The most common method creating an instance is using the Spring Security namespace. For - * example, the {@code pattern} and {@code access} attributes of the - * {@code } elements defined as children of the {@code } element are - * combined to build the instance used by the {@code FilterSecurityInterceptor}. - * - * @author Ben Alex - * @author Luke Taylor - * @deprecated In modern Spring Security APIs, each API manages its own configuration - * context. As such there is no direct replacement for this interface. In the case of - * method security, please see {@link SecurityAnnotationScanner} and - * {@link AuthorizationManager}. In the case of channel security, please see - * {@code HttpsRedirectFilter}. In the case of web security, please see - * {@link AuthorizationManager}. - */ -@Deprecated -public class DefaultFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { - - protected final Log logger = LogFactory.getLog(getClass()); - - private final Map> requestMap; - - /** - * Sets the internal request map from the supplied map. The key elements should be of - * type {@link RequestMatcher}, which. The path stored in the key will depend on the - * type of the supplied UrlMatcher. - * @param requestMap order-preserving map of request definitions to attribute lists - */ - public DefaultFilterInvocationSecurityMetadataSource( - LinkedHashMap> requestMap) { - this.requestMap = requestMap; - } - - @Override - public Collection getAllConfigAttributes() { - Set allAttributes = new HashSet<>(); - this.requestMap.values().forEach(allAttributes::addAll); - return allAttributes; - } - - @Override - public Collection getAttributes(Object object) { - final HttpServletRequest request = getHttpServletRequest(object); - int count = 0; - for (Map.Entry> entry : this.requestMap.entrySet()) { - if (entry.getKey().matches(request)) { - return entry.getValue(); - } - else { - if (this.logger.isTraceEnabled()) { - this.logger.trace(LogMessage.format("Did not match request to %s - %s (%d/%d)", entry.getKey(), - entry.getValue(), ++count, this.requestMap.size())); - } - } - } - return Collections.emptyList(); - } - - @Override - public boolean supports(Class clazz) { - return FilterInvocation.class.isAssignableFrom(clazz); - } - - private HttpServletRequest getHttpServletRequest(Object object) { - if (object instanceof FilterInvocation invocation) { - return invocation.getHttpRequest(); - } - if (object instanceof HttpServletRequest request) { - return request; - } - throw new IllegalArgumentException("object must be of type FilterInvocation or HttpServletRequest"); - } - -} diff --git a/access/src/main/java/org/springframework/security/web/access/intercept/FilterInvocationSecurityMetadataSource.java b/access/src/main/java/org/springframework/security/web/access/intercept/FilterInvocationSecurityMetadataSource.java deleted file mode 100644 index b0d26a476a0..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/intercept/FilterInvocationSecurityMetadataSource.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.intercept; - -import org.springframework.security.access.SecurityMetadataSource; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.annotation.SecurityAnnotationScanner; -import org.springframework.security.web.FilterInvocation; - -/** - * Marker interface for SecurityMetadataSource implementations that are - * designed to perform lookups keyed on {@link FilterInvocation}s. - * - * @author Ben Alex - * @deprecated In modern Spring Security APIs, each API manages its own configuration - * context. As such there is no direct replacement for this interface. In the case of - * method security, please see {@link SecurityAnnotationScanner} and - * {@link AuthorizationManager}. In the case of channel security, please see - * {@code HttpsRedirectFilter}. In the case of web security, please see - * {@link AuthorizationManager}. - */ -@Deprecated -public interface FilterInvocationSecurityMetadataSource extends SecurityMetadataSource { - -} diff --git a/access/src/main/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptor.java b/access/src/main/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptor.java deleted file mode 100644 index 3d131415440..00000000000 --- a/access/src/main/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptor.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.intercept; - -import java.io.IOException; - -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.FilterConfig; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import org.jspecify.annotations.Nullable; - -import org.springframework.security.access.SecurityMetadataSource; -import org.springframework.security.access.intercept.AbstractSecurityInterceptor; -import org.springframework.security.access.intercept.InterceptorStatusToken; -import org.springframework.security.web.FilterInvocation; - -/** - * Performs security handling of HTTP resources via a filter implementation. - *

- * The SecurityMetadataSource required by this security interceptor is of - * type {@link FilterInvocationSecurityMetadataSource}. - *

- * Refer to {@link AbstractSecurityInterceptor} for details on the workflow. - *

- * - * @author Ben Alex - * @author Rob Winch - * @deprecated Use {@link AuthorizationFilter} instead - */ -@Deprecated -public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter { - - private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied"; - - private @Nullable FilterInvocationSecurityMetadataSource securityMetadataSource; - - private boolean observeOncePerRequest = false; - - /** - * Not used (we rely on IoC container lifecycle services instead) - * @param arg0 ignored - * - */ - @Override - public void init(FilterConfig arg0) { - } - - /** - * Not used (we rely on IoC container lifecycle services instead) - */ - @Override - public void destroy() { - } - - /** - * Method that is actually called by the filter chain. Simply delegates to the - * {@link #invoke(FilterInvocation)} method. - * @param request the servlet request - * @param response the servlet response - * @param chain the filter chain - * @throws IOException if the filter chain fails - * @throws ServletException if the filter chain fails - */ - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - invoke(new FilterInvocation(request, response, chain)); - } - - public @Nullable FilterInvocationSecurityMetadataSource getSecurityMetadataSource() { - return this.securityMetadataSource; - } - - @Override - public @Nullable SecurityMetadataSource obtainSecurityMetadataSource() { - return this.securityMetadataSource; - } - - public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) { - this.securityMetadataSource = newSource; - } - - @Override - public Class getSecureObjectClass() { - return FilterInvocation.class; - } - - public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException { - if (isApplied(filterInvocation) && this.observeOncePerRequest) { - // filter already applied to this request and user wants us to observe - // once-per-request handling, so don't re-do security checking - filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse()); - return; - } - // first time this request being called, so perform security checking - if (filterInvocation.getRequest() != null && this.observeOncePerRequest) { - filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); - } - InterceptorStatusToken token = super.beforeInvocation(filterInvocation); - try { - filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse()); - } - finally { - super.finallyInvocation(token); - } - super.afterInvocation(token, null); - } - - private boolean isApplied(FilterInvocation filterInvocation) { - return (filterInvocation.getRequest() != null) - && (filterInvocation.getRequest().getAttribute(FILTER_APPLIED) != null); - } - - /** - * Indicates whether once-per-request handling will be observed. By default this is - * true, meaning the FilterSecurityInterceptor will only - * execute once-per-request. Sometimes users may wish it to execute more than once per - * request, such as when JSP forwards are being used and filter security is desired on - * each included fragment of the HTTP request. - * @return true (the default) if once-per-request is honoured, otherwise - * false if FilterSecurityInterceptor will enforce - * authorizations for each and every fragment of the HTTP request. - */ - public boolean isObserveOncePerRequest() { - return this.observeOncePerRequest; - } - - public void setObserveOncePerRequest(boolean observeOncePerRequest) { - this.observeOncePerRequest = observeOncePerRequest; - } - -} diff --git a/access/src/test/java/org/springframework/security/access/AuthenticationCredentialsNotFoundEventTests.java b/access/src/test/java/org/springframework/security/access/AuthenticationCredentialsNotFoundEventTests.java deleted file mode 100644 index ee11ef2321d..00000000000 --- a/access/src/test/java/org/springframework/security/access/AuthenticationCredentialsNotFoundEventTests.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.event.AuthenticationCredentialsNotFoundEvent; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.util.SimpleMethodInvocation; - -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests {@link AuthenticationCredentialsNotFoundEvent}. - * - * @author Ben Alex - */ -public class AuthenticationCredentialsNotFoundEventTests { - - @Test - public void testRejectsNulls() { - assertThatIllegalArgumentException().isThrownBy(() -> new AuthenticationCredentialsNotFoundEvent(null, - SecurityConfig.createList("TEST"), new AuthenticationCredentialsNotFoundException("test"))); - } - - @Test - public void testRejectsNulls2() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new AuthenticationCredentialsNotFoundEvent(new SimpleMethodInvocation(), null, - new AuthenticationCredentialsNotFoundException("test"))); - } - - @Test - public void testRejectsNulls3() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new AuthenticationCredentialsNotFoundEvent(new SimpleMethodInvocation(), - SecurityConfig.createList("TEST"), null)); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/AuthorizationFailureEventTests.java b/access/src/test/java/org/springframework/security/access/AuthorizationFailureEventTests.java deleted file mode 100644 index 519eae87cc1..00000000000 --- a/access/src/test/java/org/springframework/security/access/AuthorizationFailureEventTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access; - -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.event.AuthorizationFailureEvent; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.util.SimpleMethodInvocation; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests {@link AuthorizationFailureEvent}. - * - * @author Ben Alex - */ -public class AuthorizationFailureEventTests { - - private final UsernamePasswordAuthenticationToken foo = UsernamePasswordAuthenticationToken.unauthenticated("foo", - "bar"); - - private List attributes = SecurityConfig.createList("TEST"); - - private AccessDeniedException exception = new AuthorizationServiceException("error", new Throwable()); - - @Test - public void rejectsNullSecureObject() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new AuthorizationFailureEvent(null, this.attributes, this.foo, this.exception)); - } - - @Test - public void rejectsNullAttributesList() { - assertThatIllegalArgumentException().isThrownBy( - () -> new AuthorizationFailureEvent(new SimpleMethodInvocation(), null, this.foo, this.exception)); - } - - @Test - public void rejectsNullAuthentication() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new AuthorizationFailureEvent(new SimpleMethodInvocation(), this.attributes, null, - this.exception)); - } - - @Test - public void rejectsNullException() { - assertThatIllegalArgumentException().isThrownBy( - () -> new AuthorizationFailureEvent(new SimpleMethodInvocation(), this.attributes, this.foo, null)); - } - - @Test - public void gettersReturnCtorSuppliedData() { - AuthorizationFailureEvent event = new AuthorizationFailureEvent(new Object(), this.attributes, this.foo, - this.exception); - assertThat(event.getConfigAttributes()).isSameAs(this.attributes); - assertThat(event.getAccessDeniedException()).isSameAs(this.exception); - assertThat(event.getAuthentication()).isSameAs(this.foo); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/AuthorizedEventTests.java b/access/src/test/java/org/springframework/security/access/AuthorizedEventTests.java deleted file mode 100644 index c5655ec2822..00000000000 --- a/access/src/test/java/org/springframework/security/access/AuthorizedEventTests.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.event.AuthorizedEvent; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.util.SimpleMethodInvocation; - -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests {@link AuthorizedEvent}. - * - * @author Ben Alex - */ -public class AuthorizedEventTests { - - @Test - public void testRejectsNulls() { - assertThatIllegalArgumentException().isThrownBy(() -> new AuthorizedEvent(null, - SecurityConfig.createList("TEST"), UsernamePasswordAuthenticationToken.unauthenticated("foo", "bar"))); - } - - @Test - public void testRejectsNulls2() { - assertThatIllegalArgumentException().isThrownBy(() -> new AuthorizedEvent(new SimpleMethodInvocation(), null, - UsernamePasswordAuthenticationToken.unauthenticated("foo", "bar"))); - } - - @Test - public void testRejectsNulls3() { - assertThatIllegalArgumentException().isThrownBy( - () -> new AuthorizedEvent(new SimpleMethodInvocation(), SecurityConfig.createList("TEST"), null)); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/ITargetObject.java b/access/src/test/java/org/springframework/security/access/ITargetObject.java deleted file mode 100644 index 9929eb53bad..00000000000 --- a/access/src/test/java/org/springframework/security/access/ITargetObject.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access; - -/** - * Represents the interface of a secured object. - * - * @author Ben Alex - */ -public interface ITargetObject { - - Integer computeHashCode(String input); - - int countLength(String input); - - String makeLowerCase(String input); - - String makeUpperCase(String input); - - String publicMakeLowerCase(String input); - -} diff --git a/access/src/test/java/org/springframework/security/access/OtherTargetObject.java b/access/src/test/java/org/springframework/security/access/OtherTargetObject.java deleted file mode 100644 index c4ea9a2fb3c..00000000000 --- a/access/src/test/java/org/springframework/security/access/OtherTargetObject.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access; - -/** - * Simply extends {@link TargetObject} so we have a different object to put configuration - * attributes against. - *

- * There is no different behaviour. We have to define each method so that - * Class.getMethod(methodName, args) returns a Method - * referencing this class rather than the parent class. - *

- *

- * We need to implement ITargetObject again because the - * MethodDefinitionAttributes only locates attributes on interfaces - * explicitly defined by the intercepted class (not the interfaces defined by its parent - * class or classes). - *

- * - * @author Ben Alex - */ -public class OtherTargetObject extends TargetObject implements ITargetObject { - - @Override - public String makeLowerCase(String input) { - return super.makeLowerCase(input); - } - - @Override - public String makeUpperCase(String input) { - return super.makeUpperCase(input); - } - - @Override - public String publicMakeLowerCase(String input) { - return super.publicMakeLowerCase(input); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/SecurityConfigTests.java b/access/src/test/java/org/springframework/security/access/SecurityConfigTests.java deleted file mode 100644 index 469ce214bd8..00000000000 --- a/access/src/test/java/org/springframework/security/access/SecurityConfigTests.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests {@link SecurityConfig}. - * - * @author Ben Alex - */ -public class SecurityConfigTests { - - @Test - public void testHashCode() { - SecurityConfig config = new SecurityConfig("TEST"); - assertThat(config.hashCode()).isEqualTo("TEST".hashCode()); - } - - @Test - public void testCannotConstructWithNullAttribute() { - assertThatIllegalArgumentException().isThrownBy(() -> new SecurityConfig(null)); // SEC-727 - } - - @Test - public void testCannotConstructWithEmptyAttribute() { - assertThatIllegalArgumentException().isThrownBy(() -> new SecurityConfig("")); // SEC-727 - } - - @Test - public void testNoArgConstructorDoesntExist() throws Exception { - assertThatExceptionOfType(NoSuchMethodException.class) - .isThrownBy(() -> SecurityConfig.class.getDeclaredConstructor((Class[]) null)); - } - - @Test - public void testObjectEquals() { - SecurityConfig security1 = new SecurityConfig("TEST"); - SecurityConfig security2 = new SecurityConfig("TEST"); - assertThat(security2).isEqualTo(security1); - // SEC-311: Must observe symmetry requirement of Object.equals(Object) contract - String securityString1 = "TEST"; - assertThat(securityString1).isNotSameAs(security1); - String securityString2 = "NOT_EQUAL"; - assertThat(!security1.equals(securityString2)).isTrue(); - SecurityConfig security3 = new SecurityConfig("NOT_EQUAL"); - assertThat(!security1.equals(security3)).isTrue(); - MockConfigAttribute mock1 = new MockConfigAttribute("TEST"); - assertThat(security1).isEqualTo(mock1); - MockConfigAttribute mock2 = new MockConfigAttribute("NOT_EQUAL"); - assertThat(security1).isNotEqualTo(mock2); - Integer int1 = 987; - assertThat(security1).isNotEqualTo(int1); - } - - @Test - public void testToString() { - SecurityConfig config = new SecurityConfig("TEST"); - assertThat(config.toString()).isEqualTo("TEST"); - } - - private class MockConfigAttribute implements ConfigAttribute { - - private String attribute; - - MockConfigAttribute(String configuration) { - this.attribute = configuration; - } - - @Override - public String getAttribute() { - return this.attribute; - } - - } - -} diff --git a/access/src/test/java/org/springframework/security/access/TargetObject.java b/access/src/test/java/org/springframework/security/access/TargetObject.java deleted file mode 100644 index 7af0aff10d2..00000000000 --- a/access/src/test/java/org/springframework/security/access/TargetObject.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access; - -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; - -/** - * Represents a secured object. - * - * @author Ben Alex - */ -public class TargetObject implements ITargetObject { - - @Override - public Integer computeHashCode(String input) { - return input.hashCode(); - } - - @Override - public int countLength(String input) { - return input.length(); - } - - /** - * Returns the lowercase string, followed by security environment information. - * @param input the message to make lowercase - * @return the lowercase message, a space, the Authentication class that - * was on the SecurityContext at the time of method invocation, and a - * boolean indicating if the Authentication object is authenticated or - * not - */ - @Override - public String makeLowerCase(String input) { - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - if (auth == null) { - return input.toLowerCase() + " Authentication empty"; - } - else { - return input.toLowerCase() + " " + auth.getClass().getName() + " " + auth.isAuthenticated(); - } - } - - /** - * Returns the uppercase string, followed by security environment information. - * @param input the message to make uppercase - * @return the uppercase message, a space, the Authentication class that - * was on the SecurityContext at the time of method invocation, and a - * boolean indicating if the Authentication object is authenticated or - * not - */ - @Override - public String makeUpperCase(String input) { - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - return input.toUpperCase() + " " + auth.getClass().getName() + " " + auth.isAuthenticated(); - } - - /** - * Delegates through to the {@link #makeLowerCase(String)} method. - * @param input the message to be made lower-case - */ - @Override - public String publicMakeLowerCase(String input) { - return this.makeLowerCase(input); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/annotation/BusinessService.java b/access/src/test/java/org/springframework/security/access/annotation/BusinessService.java deleted file mode 100644 index cafb805b1c9..00000000000 --- a/access/src/test/java/org/springframework/security/access/annotation/BusinessService.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.annotation; - -import java.io.Serializable; -import java.util.List; - -import jakarta.annotation.security.PermitAll; -import jakarta.annotation.security.RolesAllowed; - -import org.springframework.security.access.prepost.PreAuthorize; - -/** - */ -@Secured({ "ROLE_USER" }) -@PermitAll -public interface BusinessService extends Serializable { - - @Secured({ "ROLE_ADMIN" }) - @RolesAllowed({ "ROLE_ADMIN" }) - @PreAuthorize("hasRole('ROLE_ADMIN')") - void someAdminMethod(); - - @Secured({ "ROLE_USER", "ROLE_ADMIN" }) - @RolesAllowed({ "ROLE_USER", "ROLE_ADMIN" }) - void someUserAndAdminMethod(); - - @Secured({ "ROLE_USER" }) - @RolesAllowed({ "ROLE_USER" }) - void someUserMethod1(); - - @Secured({ "ROLE_USER" }) - @RolesAllowed({ "ROLE_USER" }) - void someUserMethod2(); - - @RolesAllowed({ "USER" }) - void rolesAllowedUser(); - - int someOther(String s); - - int someOther(int input); - - List methodReturningAList(List someList); - - Object[] methodReturningAnArray(Object[] someArray); - - List methodReturningAList(String userName, String extraParam); - - @RequireAdminRole - @RequireUserRole - default void repeatedAnnotations() { - - } - -} diff --git a/access/src/test/java/org/springframework/security/access/annotation/BusinessServiceImpl.java b/access/src/test/java/org/springframework/security/access/annotation/BusinessServiceImpl.java deleted file mode 100644 index 9a56d20c3d2..00000000000 --- a/access/src/test/java/org/springframework/security/access/annotation/BusinessServiceImpl.java +++ /dev/null @@ -1,81 +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.access.annotation; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author Joe Scalise - */ -@SuppressWarnings("serial") -public class BusinessServiceImpl implements BusinessService { - - @Override - @Secured({ "ROLE_USER" }) - public void someUserMethod1() { - } - - @Override - @Secured({ "ROLE_USER" }) - public void someUserMethod2() { - } - - @Override - @Secured({ "ROLE_USER", "ROLE_ADMIN" }) - public void someUserAndAdminMethod() { - } - - @Override - @Secured({ "ROLE_ADMIN" }) - public void someAdminMethod() { - } - - public E someUserMethod3(final E entity) { - return entity; - } - - @Override - public int someOther(String s) { - return 0; - } - - @Override - public int someOther(int input) { - return input; - } - - @Override - public List methodReturningAList(List someList) { - return someList; - } - - @Override - public List methodReturningAList(String userName, String arg2) { - return new ArrayList<>(); - } - - @Override - public Object[] methodReturningAnArray(Object[] someArray) { - return null; - } - - @Override - public void rolesAllowedUser() { - } - -} diff --git a/access/src/test/java/org/springframework/security/access/annotation/Entity.java b/access/src/test/java/org/springframework/security/access/annotation/Entity.java deleted file mode 100644 index 98f57a634e2..00000000000 --- a/access/src/test/java/org/springframework/security/access/annotation/Entity.java +++ /dev/null @@ -1,30 +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.access.annotation; - -/** - * Class to act as a superclass for annotations testing. - * - * @author Ben Alex - * - */ -public class Entity { - - public Entity(String someParameter) { - } - -} diff --git a/access/src/test/java/org/springframework/security/access/annotation/ExpressionProtectedBusinessServiceImpl.java b/access/src/test/java/org/springframework/security/access/annotation/ExpressionProtectedBusinessServiceImpl.java deleted file mode 100644 index 28f4aa8c2b8..00000000000 --- a/access/src/test/java/org/springframework/security/access/annotation/ExpressionProtectedBusinessServiceImpl.java +++ /dev/null @@ -1,81 +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.access.annotation; - -import java.util.ArrayList; -import java.util.List; - -import org.springframework.security.access.prepost.PostFilter; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.access.prepost.PreFilter; - -@SuppressWarnings("serial") -public class ExpressionProtectedBusinessServiceImpl implements BusinessService { - - @Override - public void someAdminMethod() { - } - - @Override - public int someOther(String s) { - return 0; - } - - @Override - public int someOther(int input) { - return 0; - } - - @Override - public void someUserAndAdminMethod() { - } - - @Override - public void someUserMethod1() { - } - - @Override - public void someUserMethod2() { - } - - @Override - @PreFilter(filterTarget = "someList", value = "filterObject == authentication.name or filterObject == 'sam'") - @PostFilter("filterObject == 'bob'") - public List methodReturningAList(List someList) { - return someList; - } - - @Override - public List methodReturningAList(String userName, String arg2) { - return new ArrayList<>(); - } - - @Override - @PostFilter("filterObject == 'bob'") - public Object[] methodReturningAnArray(Object[] someArray) { - return someArray; - } - - @PreAuthorize("#x == 'x' and @number.intValue() == 1294 ") - public void methodWithBeanNamePropertyAccessExpression(String x) { - } - - @Override - public void rolesAllowedUser() { - } - -} diff --git a/access/src/test/java/org/springframework/security/access/annotation/Jsr250BusinessServiceImpl.java b/access/src/test/java/org/springframework/security/access/annotation/Jsr250BusinessServiceImpl.java deleted file mode 100644 index 02d365b1306..00000000000 --- a/access/src/test/java/org/springframework/security/access/annotation/Jsr250BusinessServiceImpl.java +++ /dev/null @@ -1,82 +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.access.annotation; - -import java.util.ArrayList; -import java.util.List; - -import jakarta.annotation.security.PermitAll; -import jakarta.annotation.security.RolesAllowed; - -/** - * @author Luke Taylor - */ -@PermitAll -@SuppressWarnings("serial") -public class Jsr250BusinessServiceImpl implements BusinessService { - - @Override - @RolesAllowed("ROLE_USER") - public void someUserMethod1() { - } - - @Override - @RolesAllowed("ROLE_USER") - public void someUserMethod2() { - } - - @Override - @RolesAllowed({ "ROLE_USER", "ROLE_ADMIN" }) - public void someUserAndAdminMethod() { - } - - @Override - @RolesAllowed("ROLE_ADMIN") - public void someAdminMethod() { - } - - @Override - public int someOther(String input) { - return 0; - } - - @Override - public int someOther(int input) { - return input; - } - - @Override - public List methodReturningAList(List someList) { - return someList; - } - - @Override - public List methodReturningAList(String userName, String arg2) { - return new ArrayList<>(); - } - - @Override - public Object[] methodReturningAnArray(Object[] someArray) { - return null; - } - - @Override - @RolesAllowed({ "USER" }) - public void rolesAllowedUser() { - } - -} diff --git a/access/src/test/java/org/springframework/security/access/annotation/Jsr250MethodSecurityMetadataSourceTests.java b/access/src/test/java/org/springframework/security/access/annotation/Jsr250MethodSecurityMetadataSourceTests.java deleted file mode 100644 index 957d92e5f72..00000000000 --- a/access/src/test/java/org/springframework/security/access/annotation/Jsr250MethodSecurityMetadataSourceTests.java +++ /dev/null @@ -1,271 +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.access.annotation; - -import java.util.Collection; - -import jakarta.annotation.security.PermitAll; -import jakarta.annotation.security.RolesAllowed; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.intercept.method.MockMethodInvocation; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Luke Taylor - * @author Ben Alex - */ -public class Jsr250MethodSecurityMetadataSourceTests { - - Jsr250MethodSecurityMetadataSource mds; - - A a; - - UserAllowedClass userAllowed; - - @BeforeEach - public void setup() { - this.mds = new Jsr250MethodSecurityMetadataSource(); - this.a = new A(); - this.userAllowed = new UserAllowedClass(); - } - - private ConfigAttribute[] findAttributes(String methodName) throws Exception { - return this.mds.findAttributes(this.a.getClass().getMethod(methodName), null).toArray(new ConfigAttribute[0]); - } - - @Test - public void methodWithRolesAllowedHasCorrectAttribute() throws Exception { - ConfigAttribute[] accessAttributes = findAttributes("adminMethod"); - assertThat(accessAttributes).hasSize(1); - assertThat(accessAttributes[0].toString()).isEqualTo("ROLE_ADMIN"); - } - - @Test - public void permitAllMethodHasPermitAllAttribute() throws Exception { - ConfigAttribute[] accessAttributes = findAttributes("permitAllMethod"); - assertThat(accessAttributes).hasSize(1); - assertThat(accessAttributes[0].toString()).isEqualTo("jakarta.annotation.security.PermitAll"); - } - - @Test - public void noRoleMethodHasNoAttributes() throws Exception { - Collection accessAttributes = this.mds - .findAttributes(this.a.getClass().getMethod("noRoleMethod"), null); - assertThat(accessAttributes).isNull(); - } - - @Test - public void classRoleIsAppliedToNoRoleMethod() throws Exception { - Collection accessAttributes = this.mds - .findAttributes(this.userAllowed.getClass().getMethod("noRoleMethod"), null); - assertThat(accessAttributes).isNull(); - } - - @Test - public void methodRoleOverridesClassRole() throws Exception { - Collection accessAttributes = this.mds - .findAttributes(this.userAllowed.getClass().getMethod("adminMethod"), null); - assertThat(accessAttributes).hasSize(1); - assertThat(accessAttributes.toArray()[0].toString()).isEqualTo("ROLE_ADMIN"); - } - - @Test - public void customDefaultRolePrefix() throws Exception { - this.mds.setDefaultRolePrefix("CUSTOMPREFIX_"); - ConfigAttribute[] accessAttributes = findAttributes("adminMethod"); - assertThat(accessAttributes).hasSize(1); - assertThat(accessAttributes[0].toString()).isEqualTo("CUSTOMPREFIX_ADMIN"); - } - - @Test - public void emptyDefaultRolePrefix() throws Exception { - this.mds.setDefaultRolePrefix(""); - ConfigAttribute[] accessAttributes = findAttributes("adminMethod"); - assertThat(accessAttributes).hasSize(1); - assertThat(accessAttributes[0].toString()).isEqualTo("ADMIN"); - } - - @Test - public void nullDefaultRolePrefix() throws Exception { - this.mds.setDefaultRolePrefix(null); - ConfigAttribute[] accessAttributes = findAttributes("adminMethod"); - assertThat(accessAttributes).hasSize(1); - assertThat(accessAttributes[0].toString()).isEqualTo("ADMIN"); - } - - @Test - public void alreadyHasDefaultPrefix() throws Exception { - ConfigAttribute[] accessAttributes = findAttributes("roleAdminMethod"); - assertThat(accessAttributes).hasSize(1); - assertThat(accessAttributes[0].toString()).isEqualTo("ROLE_ADMIN"); - } - - // JSR-250 Spec Tests - /** - * Class-level annotations only affect the class they annotate and their members, that - * is, its methods and fields. They never affect a member declared by a superclass, - * even if it is not hidden or overridden by the class in question. - * @throws Exception - */ - @Test - public void classLevelAnnotationsOnlyAffectTheClassTheyAnnotateAndTheirMembers() throws Exception { - Child target = new Child(); - MockMethodInvocation mi = new MockMethodInvocation(target, target.getClass(), "notOverriden"); - Collection accessAttributes = this.mds.getAttributes(mi); - assertThat(accessAttributes).isNull(); - } - - @Test - public void classLevelAnnotationsOnlyAffectTheClassTheyAnnotateAndTheirMembersOverriden() throws Exception { - Child target = new Child(); - MockMethodInvocation mi = new MockMethodInvocation(target, target.getClass(), "overriden"); - Collection accessAttributes = this.mds.getAttributes(mi); - assertThat(accessAttributes).hasSize(1); - assertThat(accessAttributes.toArray()[0].toString()).isEqualTo("ROLE_DERIVED"); - } - - @Test - public void classLevelAnnotationsImpactMemberLevel() throws Exception { - Child target = new Child(); - MockMethodInvocation mi = new MockMethodInvocation(target, target.getClass(), "defaults"); - Collection accessAttributes = this.mds.getAttributes(mi); - assertThat(accessAttributes).hasSize(1); - assertThat(accessAttributes.toArray()[0].toString()).isEqualTo("ROLE_DERIVED"); - } - - @Test - public void classLevelAnnotationsIgnoredByExplicitMemberAnnotation() throws Exception { - Child target = new Child(); - MockMethodInvocation mi = new MockMethodInvocation(target, target.getClass(), "explicitMethod"); - Collection accessAttributes = this.mds.getAttributes(mi); - assertThat(accessAttributes).hasSize(1); - assertThat(accessAttributes.toArray()[0].toString()).isEqualTo("ROLE_EXPLICIT"); - } - - /** - * The interfaces implemented by a class never contribute annotations to the class - * itself or any of its members. - * @throws Exception - */ - @Test - public void interfacesNeverContributeAnnotationsMethodLevel() throws Exception { - Parent target = new Parent(); - MockMethodInvocation mi = new MockMethodInvocation(target, target.getClass(), "interfaceMethod"); - Collection accessAttributes = this.mds.getAttributes(mi); - assertThat(accessAttributes).isEmpty(); - } - - @Test - public void interfacesNeverContributeAnnotationsClassLevel() throws Exception { - Parent target = new Parent(); - MockMethodInvocation mi = new MockMethodInvocation(target, target.getClass(), "notOverriden"); - Collection accessAttributes = this.mds.getAttributes(mi); - assertThat(accessAttributes).isEmpty(); - } - - @Test - public void annotationsOnOverriddenMemberIgnored() throws Exception { - Child target = new Child(); - MockMethodInvocation mi = new MockMethodInvocation(target, target.getClass(), "overridenIgnored"); - Collection accessAttributes = this.mds.getAttributes(mi); - assertThat(accessAttributes).hasSize(1); - assertThat(accessAttributes.toArray()[0].toString()).isEqualTo("ROLE_DERIVED"); - } - - public static class A { - - public void noRoleMethod() { - } - - @RolesAllowed("ADMIN") - public void adminMethod() { - } - - @RolesAllowed("ROLE_ADMIN") - public void roleAdminMethod() { - } - - @PermitAll - public void permitAllMethod() { - } - - } - - @RolesAllowed("USER") - public static class UserAllowedClass { - - public void noRoleMethod() { - } - - @RolesAllowed("ADMIN") - public void adminMethod() { - } - - } - - // JSR-250 Spec - @RolesAllowed("IPARENT") - interface IParent { - - @RolesAllowed("INTERFACEMETHOD") - void interfaceMethod(); - - } - - static class Parent implements IParent { - - @Override - public void interfaceMethod() { - } - - public void notOverriden() { - } - - public void overriden() { - } - - @RolesAllowed("OVERRIDENIGNORED") - public void overridenIgnored() { - } - - } - - @RolesAllowed("DERIVED") - class Child extends Parent { - - @Override - public void overriden() { - } - - @Override - public void overridenIgnored() { - } - - public void defaults() { - } - - @RolesAllowed("EXPLICIT") - public void explicitMethod() { - } - - } - -} diff --git a/access/src/test/java/org/springframework/security/access/annotation/Jsr250VoterTests.java b/access/src/test/java/org/springframework/security/access/annotation/Jsr250VoterTests.java deleted file mode 100644 index eba794053d8..00000000000 --- a/access/src/test/java/org/springframework/security/access/annotation/Jsr250VoterTests.java +++ /dev/null @@ -1,57 +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.access.annotation; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.authentication.TestingAuthenticationToken; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Luke Taylor - */ -public class Jsr250VoterTests { - - // SEC-1443 - @Test - public void supportsMultipleRolesCorrectly() { - List attrs = new ArrayList<>(); - Jsr250Voter voter = new Jsr250Voter(); - attrs.add(new Jsr250SecurityConfig("A")); - attrs.add(new Jsr250SecurityConfig("B")); - attrs.add(new Jsr250SecurityConfig("C")); - assertThat(voter.vote(new TestingAuthenticationToken("user", "pwd", "A"), new Object(), attrs)) - .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); - assertThat(voter.vote(new TestingAuthenticationToken("user", "pwd", "B"), new Object(), attrs)) - .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); - assertThat(voter.vote(new TestingAuthenticationToken("user", "pwd", "C"), new Object(), attrs)) - .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); - assertThat(voter.vote(new TestingAuthenticationToken("user", "pwd", "NONE"), new Object(), attrs)) - .isEqualTo(AccessDecisionVoter.ACCESS_DENIED); - assertThat(voter.vote(new TestingAuthenticationToken("user", "pwd", "A"), new Object(), - SecurityConfig.createList("A", "B", "C"))) - .isEqualTo(AccessDecisionVoter.ACCESS_ABSTAIN); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/annotation/RequireAdminRole.java b/access/src/test/java/org/springframework/security/access/annotation/RequireAdminRole.java deleted file mode 100644 index 9512063decb..00000000000 --- a/access/src/test/java/org/springframework/security/access/annotation/RequireAdminRole.java +++ /dev/null @@ -1,32 +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.access.annotation; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import jakarta.annotation.security.RolesAllowed; - -import org.springframework.security.access.prepost.PreAuthorize; - -@Retention(RetentionPolicy.RUNTIME) -@PreAuthorize("hasRole('ADMIN')") -@RolesAllowed("ADMIN") -@Secured("ADMIN") -public @interface RequireAdminRole { - -} diff --git a/access/src/test/java/org/springframework/security/access/annotation/RequireUserRole.java b/access/src/test/java/org/springframework/security/access/annotation/RequireUserRole.java deleted file mode 100644 index bcb1d1e9f8c..00000000000 --- a/access/src/test/java/org/springframework/security/access/annotation/RequireUserRole.java +++ /dev/null @@ -1,32 +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.access.annotation; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import jakarta.annotation.security.RolesAllowed; - -import org.springframework.security.access.prepost.PreAuthorize; - -@Retention(RetentionPolicy.RUNTIME) -@PreAuthorize("hasRole('USER')") -@RolesAllowed("USER") -@Secured("USER") -public @interface RequireUserRole { - -} diff --git a/access/src/test/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataSourceTests.java b/access/src/test/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataSourceTests.java deleted file mode 100644 index e4b5235f165..00000000000 --- a/access/src/test/java/org/springframework/security/access/annotation/SecuredAnnotationSecurityMetadataSourceTests.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.annotation; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collection; -import java.util.EnumSet; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.access.annotation.sec2150.MethodInvocationFactory; -import org.springframework.security.access.intercept.method.MockMethodInvocation; -import org.springframework.security.core.GrantedAuthority; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -/** - * Tests for {@link SecuredAnnotationSecurityMetadataSource} - * - * @author Mark St.Godard - * @author Joe Scalise - * @author Ben Alex - * @author Luke Taylor - */ -public class SecuredAnnotationSecurityMetadataSourceTests { - - private SecuredAnnotationSecurityMetadataSource mds = new SecuredAnnotationSecurityMetadataSource(); - - @Test - public void genericsSuperclassDeclarationsAreIncludedWhenSubclassesOverride() { - Method method = null; - try { - method = DepartmentServiceImpl.class.getMethod("someUserMethod3", new Class[] { Department.class }); - } - catch (NoSuchMethodException unexpected) { - fail("Should be a superMethod called 'someUserMethod3' on class!"); - } - Collection attrs = this.mds.findAttributes(method, DepartmentServiceImpl.class); - assertThat(attrs).isNotNull(); - // expect 1 attribute - assertThat(attrs.size() == 1).as("Did not find 1 attribute").isTrue(); - // should have 1 SecurityConfig - for (ConfigAttribute sc : attrs) { - assertThat(sc.getAttribute()).as("Found an incorrect role").isEqualTo("ROLE_ADMIN"); - } - Method superMethod = null; - try { - superMethod = DepartmentServiceImpl.class.getMethod("someUserMethod3", new Class[] { Entity.class }); - } - catch (NoSuchMethodException unexpected) { - fail("Should be a superMethod called 'someUserMethod3' on class!"); - } - Collection superAttrs = this.mds.findAttributes(superMethod, DepartmentServiceImpl.class); - assertThat(superAttrs).isNotNull(); - // This part of the test relates to SEC-274 - // expect 1 attribute - assertThat(superAttrs).as("Did not find 1 attribute").hasSize(1); - // should have 1 SecurityConfig - for (ConfigAttribute sc : superAttrs) { - assertThat(sc.getAttribute()).as("Found an incorrect role").isEqualTo("ROLE_ADMIN"); - } - } - - @Test - public void classLevelAttributesAreFound() { - Collection attrs = this.mds.findAttributes(BusinessService.class); - assertThat(attrs).isNotNull(); - // expect 1 annotation - assertThat(attrs).hasSize(1); - // should have 1 SecurityConfig - SecurityConfig sc = (SecurityConfig) attrs.toArray()[0]; - assertThat(sc.getAttribute()).isEqualTo("ROLE_USER"); - } - - @Test - public void methodLevelAttributesAreFound() { - Method method = null; - try { - method = BusinessService.class.getMethod("someUserAndAdminMethod", new Class[] {}); - } - catch (NoSuchMethodException unexpected) { - fail("Should be a method called 'someUserAndAdminMethod' on class!"); - } - Collection attrs = this.mds.findAttributes(method, BusinessService.class); - // expect 2 attributes - assertThat(attrs).hasSize(2); - boolean user = false; - boolean admin = false; - // should have 2 SecurityConfigs - for (ConfigAttribute sc : attrs) { - assertThat(sc).isInstanceOf(SecurityConfig.class); - if (sc.getAttribute().equals("ROLE_USER")) { - user = true; - } - else if (sc.getAttribute().equals("ROLE_ADMIN")) { - admin = true; - } - } - // expect to have ROLE_USER and ROLE_ADMIN - assertThat(user).isEqualTo(admin).isTrue(); - } - - // SEC-1491 - @Test - public void customAnnotationAttributesAreFound() { - SecuredAnnotationSecurityMetadataSource mds = new SecuredAnnotationSecurityMetadataSource( - new CustomSecurityAnnotationMetadataExtractor()); - Collection attrs = mds.findAttributes(CustomAnnotatedService.class); - assertThat(attrs).containsOnly(SecurityEnum.ADMIN); - } - - @Test - public void annotatedAnnotationAtClassLevelIsDetected() throws Exception { - MockMethodInvocation annotatedAtClassLevel = new MockMethodInvocation(new AnnotatedAnnotationAtClassLevel(), - ReturnVoid.class, "doSomething", List.class); - ConfigAttribute[] attrs = this.mds.getAttributes(annotatedAtClassLevel).toArray(new ConfigAttribute[0]); - assertThat(attrs).hasSize(1); - assertThat(attrs).extracting("attribute").containsOnly("CUSTOM"); - } - - @Test - public void annotatedAnnotationAtInterfaceLevelIsDetected() throws Exception { - MockMethodInvocation annotatedAtInterfaceLevel = new MockMethodInvocation( - new AnnotatedAnnotationAtInterfaceLevel(), ReturnVoid2.class, "doSomething", List.class); - ConfigAttribute[] attrs = this.mds.getAttributes(annotatedAtInterfaceLevel).toArray(new ConfigAttribute[0]); - assertThat(attrs).hasSize(1); - assertThat(attrs).extracting("attribute").containsOnly("CUSTOM"); - } - - @Test - public void annotatedAnnotationAtMethodLevelIsDetected() throws Exception { - MockMethodInvocation annotatedAtMethodLevel = new MockMethodInvocation(new AnnotatedAnnotationAtMethodLevel(), - ReturnVoid.class, "doSomething", List.class); - ConfigAttribute[] attrs = this.mds.getAttributes(annotatedAtMethodLevel).toArray(new ConfigAttribute[0]); - assertThat(attrs).hasSize(1); - assertThat(attrs).extracting("attribute").containsOnly("CUSTOM"); - } - - @Test - public void proxyFactoryInterfaceAttributesFound() throws Exception { - MockMethodInvocation mi = MethodInvocationFactory.createSec2150MethodInvocation(); - Collection attributes = this.mds.getAttributes(mi); - assertThat(attributes).hasSize(1); - assertThat(attributes).extracting("attribute").containsOnly("ROLE_PERSON"); - } - - // Inner classes - class Department extends Entity { - - Department(String name) { - super(name); - } - - } - - interface DepartmentService extends BusinessService { - - @Secured({ "ROLE_USER" }) - Department someUserMethod3(Department dept); - - } - - @SuppressWarnings("serial") - class DepartmentServiceImpl extends BusinessServiceImpl implements DepartmentService { - - @Override - @Secured({ "ROLE_ADMIN" }) - public Department someUserMethod3(final Department dept) { - return super.someUserMethod3(dept); - } - - } - - // SEC-1491 Related classes. PoC for custom annotation with enum value. - @CustomSecurityAnnotation(SecurityEnum.ADMIN) - interface CustomAnnotatedService { - - } - - class CustomAnnotatedServiceImpl implements CustomAnnotatedService { - - } - - enum SecurityEnum implements ConfigAttribute, GrantedAuthority { - - ADMIN, USER; - - @Override - public String getAttribute() { - return toString(); - } - - @Override - public String getAuthority() { - return toString(); - } - - } - - @Target({ ElementType.METHOD, ElementType.TYPE }) - @Retention(RetentionPolicy.RUNTIME) - @interface CustomSecurityAnnotation { - - SecurityEnum[] value(); - - } - - class CustomSecurityAnnotationMetadataExtractor implements AnnotationMetadataExtractor { - - @Override - public Collection extractAttributes(CustomSecurityAnnotation securityAnnotation) { - SecurityEnum[] values = securityAnnotation.value(); - return EnumSet.copyOf(Arrays.asList(values)); - } - - } - - @Target({ ElementType.METHOD, ElementType.TYPE }) - @Retention(RetentionPolicy.RUNTIME) - @Inherited - @Secured("CUSTOM") - public @interface AnnotatedAnnotation { - - } - - public interface ReturnVoid { - - void doSomething(List param); - - } - - @AnnotatedAnnotation - public interface ReturnVoid2 { - - void doSomething(List param); - - } - - @AnnotatedAnnotation - public static class AnnotatedAnnotationAtClassLevel implements ReturnVoid { - - @Override - public void doSomething(List param) { - } - - } - - public static class AnnotatedAnnotationAtInterfaceLevel implements ReturnVoid2 { - - @Override - public void doSomething(List param) { - } - - } - - public static class AnnotatedAnnotationAtMethodLevel implements ReturnVoid { - - @Override - @AnnotatedAnnotation - public void doSomething(List param) { - } - - } - -} diff --git a/access/src/test/java/org/springframework/security/access/annotation/sec2150/CrudRepository.java b/access/src/test/java/org/springframework/security/access/annotation/sec2150/CrudRepository.java deleted file mode 100644 index 078cc3c5998..00000000000 --- a/access/src/test/java/org/springframework/security/access/annotation/sec2150/CrudRepository.java +++ /dev/null @@ -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. - */ - -package org.springframework.security.access.annotation.sec2150; - -public interface CrudRepository { - - Iterable findAll(); - -} diff --git a/access/src/test/java/org/springframework/security/access/annotation/sec2150/MethodInvocationFactory.java b/access/src/test/java/org/springframework/security/access/annotation/sec2150/MethodInvocationFactory.java deleted file mode 100644 index 4d63efa5a82..00000000000 --- a/access/src/test/java/org/springframework/security/access/annotation/sec2150/MethodInvocationFactory.java +++ /dev/null @@ -1,40 +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.access.annotation.sec2150; - -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.security.access.intercept.method.MockMethodInvocation; - -public final class MethodInvocationFactory { - - private MethodInvocationFactory() { - } - - /** - * In order to reproduce the bug for SEC-2150, we must have a proxy object that - * implements TargetSourceAware and implements our annotated interface. - * @return the mock method invocation - * @throws NoSuchMethodException - */ - public static MockMethodInvocation createSec2150MethodInvocation() throws NoSuchMethodException { - ProxyFactory factory = new ProxyFactory(new Class[] { PersonRepository.class }); - factory.setTargetClass(CrudRepository.class); - PersonRepository repository = (PersonRepository) factory.getProxy(); - return new MockMethodInvocation(repository, PersonRepository.class, "findAll"); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/annotation/sec2150/PersonRepository.java b/access/src/test/java/org/springframework/security/access/annotation/sec2150/PersonRepository.java deleted file mode 100644 index 46e434895ba..00000000000 --- a/access/src/test/java/org/springframework/security/access/annotation/sec2150/PersonRepository.java +++ /dev/null @@ -1,33 +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.access.annotation.sec2150; - -import org.springframework.security.access.annotation.Secured; -import org.springframework.security.access.prepost.PreAuthorize; - -/** - * Note that JSR-256 states that annotations have no impact when placed on interfaces, so - * SEC-2150 is not impacted by JSR-256 support. - * - * @author Rob Winch - * - */ -@Secured("ROLE_PERSON") -@PreAuthorize("hasRole('ROLE_PERSON')") -public interface PersonRepository extends CrudRepository { - -} diff --git a/access/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java b/access/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java deleted file mode 100644 index e91ccb36dba..00000000000 --- a/access/src/test/java/org/springframework/security/access/expression/method/DefaultMethodSecurityExpressionHandlerTests.java +++ /dev/null @@ -1,197 +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.access.expression.method; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.aopalliance.intercept.MethodInvocation; -import org.assertj.core.api.InstanceOfAssertFactories; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.expression.TypedValue; -import org.springframework.security.access.expression.SecurityExpressionRoot; -import org.springframework.security.authentication.AuthenticationTrustResolver; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -@ExtendWith(MockitoExtension.class) -public class DefaultMethodSecurityExpressionHandlerTests { - - private DefaultMethodSecurityExpressionHandler handler; - - @Mock - private Authentication authentication; - - @Mock - private MethodInvocation methodInvocation; - - @Mock - private AuthenticationTrustResolver trustResolver; - - @BeforeEach - public void setup() { - this.handler = new DefaultMethodSecurityExpressionHandler(); - } - - private void setupMocks() { - given(this.methodInvocation.getThis()).willReturn(new Foo()); - given(this.methodInvocation.getMethod()).willReturn(Foo.class.getMethods()[0]); - } - - @AfterEach - public void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test - public void setTrustResolverNull() { - assertThatIllegalArgumentException().isThrownBy(() -> this.handler.setTrustResolver(null)); - } - - @Test - public void createEvaluationContextCustomTrustResolver() { - setupMocks(); - this.handler.setTrustResolver(this.trustResolver); - Expression expression = this.handler.getExpressionParser().parseExpression("anonymous"); - EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.methodInvocation); - expression.getValue(context, Boolean.class); - verify(this.trustResolver).isAnonymous(this.authentication); - } - - @Test - @SuppressWarnings("unchecked") - public void filterByKeyWhenUsingMapThenFiltersMap() { - setupMocks(); - final Map map = new HashMap<>(); - map.put("key1", "value1"); - map.put("key2", "value2"); - map.put("key3", "value3"); - Expression expression = this.handler.getExpressionParser().parseExpression("filterObject.key eq 'key2'"); - EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.methodInvocation); - Object filtered = this.handler.filter(map, expression, context); - assertThat(filtered == map); - Map result = ((Map) filtered); - assertThat(result.size() == 1); - assertThat(result).containsKey("key2"); - assertThat(result).containsValue("value2"); - } - - @Test - @SuppressWarnings("unchecked") - public void filterByValueWhenUsingMapThenFiltersMap() { - setupMocks(); - final Map map = new HashMap<>(); - map.put("key1", "value1"); - map.put("key2", "value2"); - map.put("key3", "value3"); - Expression expression = this.handler.getExpressionParser().parseExpression("filterObject.value eq 'value3'"); - EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.methodInvocation); - Object filtered = this.handler.filter(map, expression, context); - assertThat(filtered == map); - Map result = ((Map) filtered); - assertThat(result.size() == 1); - assertThat(result).containsKey("key3"); - assertThat(result).containsValue("value3"); - } - - @Test - @SuppressWarnings("unchecked") - public void filterByKeyAndValueWhenUsingMapThenFiltersMap() { - setupMocks(); - final Map map = new HashMap<>(); - map.put("key1", "value1"); - map.put("key2", "value2"); - map.put("key3", "value3"); - Expression expression = this.handler.getExpressionParser() - .parseExpression("(filterObject.key eq 'key1') or (filterObject.value eq 'value2')"); - EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.methodInvocation); - Object filtered = this.handler.filter(map, expression, context); - assertThat(filtered == map); - Map result = ((Map) filtered); - assertThat(result.size() == 2); - assertThat(result).containsKeys("key1", "key2"); - assertThat(result).containsValues("value1", "value2"); - } - - @Test - @SuppressWarnings("unchecked") - public void filterWhenUsingStreamThenFiltersStream() { - setupMocks(); - final Stream stream = Stream.of("1", "2", "3"); - Expression expression = this.handler.getExpressionParser().parseExpression("filterObject ne '2'"); - EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.methodInvocation); - Object filtered = this.handler.filter(stream, expression, context); - assertThat(filtered).isInstanceOf(Stream.class); - List list = ((Stream) filtered).collect(Collectors.toList()); - assertThat(list).containsExactly("1", "3"); - } - - @Test - public void filterStreamWhenClosedThenUpstreamGetsClosed() { - setupMocks(); - final Stream upstream = mock(Stream.class); - doReturn(Stream.empty()).when(upstream).filter(any()); - Expression expression = this.handler.getExpressionParser().parseExpression("true"); - EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.methodInvocation); - ((Stream) this.handler.filter(upstream, expression, context)).close(); - verify(upstream).close(); - } - - @Test - public void createEvaluationContextSupplierAuthentication() { - setupMocks(); - Supplier mockAuthenticationSupplier = mock(Supplier.class); - given(mockAuthenticationSupplier.get()).willReturn(this.authentication); - EvaluationContext context = this.handler.createEvaluationContext(mockAuthenticationSupplier, - this.methodInvocation); - verifyNoInteractions(mockAuthenticationSupplier); - assertThat(context.getRootObject()).extracting(TypedValue::getValue) - .asInstanceOf(InstanceOfAssertFactories.type(MethodSecurityExpressionRoot.class)) - .extracting(SecurityExpressionRoot::getAuthentication) - .isEqualTo(this.authentication); - verify(mockAuthenticationSupplier).get(); - } - - static class Foo { - - void bar() { - } - - } - -} diff --git a/access/src/test/java/org/springframework/security/access/expression/method/ExpressionBasedPreInvocationAdviceTests.java b/access/src/test/java/org/springframework/security/access/expression/method/ExpressionBasedPreInvocationAdviceTests.java deleted file mode 100644 index aa781eded8f..00000000000 --- a/access/src/test/java/org/springframework/security/access/expression/method/ExpressionBasedPreInvocationAdviceTests.java +++ /dev/null @@ -1,141 +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.access.expression.method; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.security.access.intercept.method.MockMethodInvocation; -import org.springframework.security.access.prepost.PreInvocationAttribute; -import org.springframework.security.core.Authentication; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests {@link ExpressionBasedPreInvocationAdvice} - * - * @author Maksim Vinogradov - * @since 5.2 - */ -@ExtendWith(MockitoExtension.class) -public class ExpressionBasedPreInvocationAdviceTests { - - @Mock - private Authentication authentication; - - private ExpressionBasedPreInvocationAdvice expressionBasedPreInvocationAdvice; - - @BeforeEach - public void setUp() { - this.expressionBasedPreInvocationAdvice = new ExpressionBasedPreInvocationAdvice(); - } - - @Test - public void findFilterTargetNameProvidedButNotMatch() throws Exception { - PreInvocationAttribute attribute = new PreInvocationExpressionAttribute("true", "filterTargetDoesNotMatch", - null); - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "doSomethingCollection", new Class[] { List.class }, new Object[] { new ArrayList<>() }); - assertThatIllegalArgumentException().isThrownBy( - () -> this.expressionBasedPreInvocationAdvice.before(this.authentication, methodInvocation, attribute)); - } - - @Test - public void findFilterTargetNameProvidedArrayUnsupported() throws Exception { - PreInvocationAttribute attribute = new PreInvocationExpressionAttribute("true", "param", null); - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "doSomethingArray", new Class[] { String[].class }, new Object[] { new String[0] }); - assertThatIllegalArgumentException().isThrownBy( - () -> this.expressionBasedPreInvocationAdvice.before(this.authentication, methodInvocation, attribute)); - } - - @Test - public void findFilterTargetNameProvided() throws Exception { - PreInvocationAttribute attribute = new PreInvocationExpressionAttribute("true", "param", null); - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "doSomethingCollection", new Class[] { List.class }, new Object[] { new ArrayList<>() }); - boolean result = this.expressionBasedPreInvocationAdvice.before(this.authentication, methodInvocation, - attribute); - assertThat(result).isTrue(); - } - - @Test - public void findFilterTargetNameNotProvidedArrayUnsupported() throws Exception { - PreInvocationAttribute attribute = new PreInvocationExpressionAttribute("true", "", null); - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "doSomethingArray", new Class[] { String[].class }, new Object[] { new String[0] }); - assertThatIllegalArgumentException().isThrownBy( - () -> this.expressionBasedPreInvocationAdvice.before(this.authentication, methodInvocation, attribute)); - } - - @Test - public void findFilterTargetNameNotProvided() throws Exception { - PreInvocationAttribute attribute = new PreInvocationExpressionAttribute("true", "", null); - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "doSomethingCollection", new Class[] { List.class }, new Object[] { new ArrayList<>() }); - boolean result = this.expressionBasedPreInvocationAdvice.before(this.authentication, methodInvocation, - attribute); - assertThat(result).isTrue(); - } - - @Test - public void findFilterTargetNameNotProvidedTypeNotSupported() throws Exception { - PreInvocationAttribute attribute = new PreInvocationExpressionAttribute("true", "", null); - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "doSomethingString", new Class[] { String.class }, new Object[] { "param" }); - assertThatIllegalArgumentException().isThrownBy( - () -> this.expressionBasedPreInvocationAdvice.before(this.authentication, methodInvocation, attribute)); - } - - @Test - public void findFilterTargetNameNotProvidedMethodAcceptMoreThenOneArgument() throws Exception { - PreInvocationAttribute attribute = new PreInvocationExpressionAttribute("true", "", null); - MockMethodInvocation methodInvocation = new MockMethodInvocation(new TestClass(), TestClass.class, - "doSomethingTwoArgs", new Class[] { String.class, List.class }, - new Object[] { "param", new ArrayList<>() }); - assertThatIllegalArgumentException().isThrownBy( - () -> this.expressionBasedPreInvocationAdvice.before(this.authentication, methodInvocation, attribute)); - } - - private class TestClass { - - public Boolean doSomethingCollection(List param) { - return Boolean.TRUE; - } - - public Boolean doSomethingArray(String[] param) { - return Boolean.TRUE; - } - - public Boolean doSomethingString(String param) { - return Boolean.TRUE; - } - - public Boolean doSomethingTwoArgs(String param, List list) { - return Boolean.TRUE; - } - - } - -} diff --git a/access/src/test/java/org/springframework/security/access/expression/method/MethodExpressionVoterTests.java b/access/src/test/java/org/springframework/security/access/expression/method/MethodExpressionVoterTests.java deleted file mode 100644 index 0f488e2fdb2..00000000000 --- a/access/src/test/java/org/springframework/security/access/expression/method/MethodExpressionVoterTests.java +++ /dev/null @@ -1,178 +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.access.expression.method; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import org.aopalliance.intercept.MethodInvocation; -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.util.SimpleMethodInvocation; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -@SuppressWarnings("unchecked") -public class MethodExpressionVoterTests { - - private TestingAuthenticationToken joe = new TestingAuthenticationToken("joe", "joespass", "ROLE_blah"); - - private PreInvocationAuthorizationAdviceVoter am = new PreInvocationAuthorizationAdviceVoter( - new ExpressionBasedPreInvocationAdvice()); - - @Test - public void hasRoleExpressionAllowsUserWithRole() throws Exception { - MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingAnArray()); - assertThat(this.am.vote(this.joe, mi, - createAttributes(new PreInvocationExpressionAttribute(null, null, "hasRole('blah')")))) - .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); - } - - @Test - public void hasRoleExpressionDeniesUserWithoutRole() throws Exception { - List cad = new ArrayList<>(1); - cad.add(new PreInvocationExpressionAttribute(null, null, "hasRole('joedoesnt')")); - MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingAnArray()); - assertThat(this.am.vote(this.joe, mi, cad)).isEqualTo(AccessDecisionVoter.ACCESS_DENIED); - } - - @Test - public void matchingArgAgainstAuthenticationNameIsSuccessful() throws Exception { - MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingAString(), "joe"); - assertThat(this.am.vote(this.joe, mi, - createAttributes(new PreInvocationExpressionAttribute(null, null, - "(#argument == principal) and (principal == 'joe')")))) - .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); - } - - @Test - public void accessIsGrantedIfNoPreAuthorizeAttributeIsUsed() throws Exception { - Collection arg = createCollectionArg("joe", "bob", "sam"); - MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingACollection(), arg); - assertThat(this.am.vote(this.joe, mi, - createAttributes(new PreInvocationExpressionAttribute("(filterObject == 'jim')", "collection", null)))) - .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); - // All objects should have been removed, because the expression is always false - assertThat(arg).isEmpty(); - } - - @Test - public void collectionPreFilteringIsSuccessful() throws Exception { - List arg = createCollectionArg("joe", "bob", "sam"); - MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingACollection(), arg); - this.am.vote(this.joe, mi, createAttributes(new PreInvocationExpressionAttribute( - "(filterObject == 'joe' or filterObject == 'sam')", "collection", "permitAll"))); - assertThat(arg).containsExactly("joe", "sam"); - } - - @Test - public void arraysCannotBePrefiltered() throws Exception { - MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingAnArray(), - createArrayArg("sam", "joe")); - assertThatIllegalArgumentException().isThrownBy(() -> this.am.vote(this.joe, mi, - createAttributes(new PreInvocationExpressionAttribute("(filterObject == 'jim')", "someArray", null)))); - } - - @Test - public void incorrectFilterTargetNameIsRejected() throws Exception { - MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingACollection(), - createCollectionArg("joe", "bob")); - assertThatIllegalArgumentException().isThrownBy(() -> this.am.vote(this.joe, mi, - createAttributes(new PreInvocationExpressionAttribute("(filterObject == 'joe')", "collcetion", null)))); - } - - @Test - public void nullNamedFilterTargetIsRejected() throws Exception { - MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingACollection(), - new Object[] { null }); - assertThatIllegalArgumentException().isThrownBy(() -> this.am.vote(this.joe, mi, - createAttributes(new PreInvocationExpressionAttribute("(filterObject == 'joe')", "collection", null)))); - } - - @Test - public void ruleDefinedInAClassMethodIsApplied() throws Exception { - MethodInvocation mi = new SimpleMethodInvocation(new TargetImpl(), methodTakingAString(), "joe"); - assertThat(this.am.vote(this.joe, mi, - createAttributes(new PreInvocationExpressionAttribute(null, null, - "T(org.springframework.security.access.expression.method.SecurityRules).isJoe(#argument)")))) - .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); - } - - private List createAttributes(ConfigAttribute... attributes) { - return Arrays.asList(attributes); - } - - private List createCollectionArg(Object... elts) { - ArrayList result = new ArrayList(elts.length); - result.addAll(Arrays.asList(elts)); - return result; - } - - private Object createArrayArg(Object... elts) { - ArrayList result = new ArrayList(elts.length); - result.addAll(Arrays.asList(elts)); - return result.toArray(new Object[0]); - } - - private Method methodTakingAnArray() throws Exception { - return Target.class.getMethod("methodTakingAnArray", Object[].class); - } - - private Method methodTakingAString() throws Exception { - return Target.class.getMethod("methodTakingAString", String.class); - } - - private Method methodTakingACollection() throws Exception { - return Target.class.getMethod("methodTakingACollection", Collection.class); - } - - private interface Target { - - void methodTakingAnArray(Object[] args); - - void methodTakingAString(String argument); - - Collection methodTakingACollection(Collection collection); - - } - - private static class TargetImpl implements Target { - - @Override - public void methodTakingAnArray(Object[] args) { - } - - @Override - public void methodTakingAString(String argument) { - }; - - @Override - public Collection methodTakingACollection(Collection collection) { - return collection; - } - - } - -} diff --git a/access/src/test/java/org/springframework/security/access/expression/method/MethodSecurityEvaluationContextTests.java b/access/src/test/java/org/springframework/security/access/expression/method/MethodSecurityEvaluationContextTests.java deleted file mode 100644 index 7cbd30d5bbf..00000000000 --- a/access/src/test/java/org/springframework/security/access/expression/method/MethodSecurityEvaluationContextTests.java +++ /dev/null @@ -1,82 +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.access.expression.method; - -import java.lang.reflect.Method; - -import org.aopalliance.intercept.MethodInvocation; -import org.jspecify.annotations.Nullable; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.core.ParameterNameDiscoverer; -import org.springframework.security.core.Authentication; -import org.springframework.util.ReflectionUtils; - -import static org.mockito.Mockito.doReturn; - -/** - * @author shabarijonnalagadda - * - */ -@ExtendWith(MockitoExtension.class) -public class MethodSecurityEvaluationContextTests { - - @Mock - private ParameterNameDiscoverer paramNameDiscoverer; - - @Mock - private Authentication authentication; - - @Mock - private MethodInvocation methodInvocation; - - @Test - public void lookupVariableWhenParameterNameNullThenNotSet() { - Class type = String.class; - Method method = ReflectionUtils.findMethod(String.class, "contains", CharSequence.class); - doReturn(new String[] { null }).when(this.paramNameDiscoverer).getParameterNames(method); - doReturn(new Object[] { null }).when(this.methodInvocation).getArguments(); - doReturn(type).when(this.methodInvocation).getThis(); - doReturn(method).when(this.methodInvocation).getMethod(); - NotNullVariableMethodSecurityEvaluationContext context = new NotNullVariableMethodSecurityEvaluationContext( - this.authentication, this.methodInvocation, this.paramNameDiscoverer); - context.lookupVariable("testVariable"); - } - - private static class NotNullVariableMethodSecurityEvaluationContext extends MethodSecurityEvaluationContext { - - NotNullVariableMethodSecurityEvaluationContext(Authentication auth, MethodInvocation mi, - ParameterNameDiscoverer parameterNameDiscoverer) { - super(auth, mi, parameterNameDiscoverer); - } - - @Override - public void setVariable(String name, @Nullable Object value) { - if (name == null) { - throw new IllegalArgumentException("name should not be null"); - } - else { - super.setVariable(name, value); - } - } - - } - -} diff --git a/access/src/test/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRootTests.java b/access/src/test/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRootTests.java deleted file mode 100644 index a908236c99a..00000000000 --- a/access/src/test/java/org/springframework/security/access/expression/method/MethodSecurityExpressionRootTests.java +++ /dev/null @@ -1,142 +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.access.expression.method; - -import org.aopalliance.intercept.MethodInvocation; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.expression.Expression; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; -import org.springframework.security.access.PermissionEvaluator; -import org.springframework.security.access.expression.ExpressionUtils; -import org.springframework.security.authentication.AuthenticationTrustResolver; -import org.springframework.security.core.Authentication; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link MethodSecurityExpressionRoot} - * - * @author Luke Taylor - */ -public class MethodSecurityExpressionRootTests { - - SpelExpressionParser parser = new SpelExpressionParser(); - - MethodSecurityExpressionRoot root; - - StandardEvaluationContext ctx; - - private AuthenticationTrustResolver trustResolver; - - private Authentication user; - - @BeforeEach - public void createContext() { - this.user = mock(Authentication.class); - this.root = new MethodSecurityExpressionRoot(() -> this.user, mock(MethodInvocation.class)); - this.ctx = new StandardEvaluationContext(); - this.ctx.setRootObject(this.root); - this.trustResolver = mock(AuthenticationTrustResolver.class); - this.root.setTrustResolver(this.trustResolver); - } - - @Test - public void canCallMethodsOnVariables() { - this.ctx.setVariable("var", "somestring"); - Expression e = this.parser.parseExpression("#var.length() == 10"); - Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue(); - } - - @Test - public void isAnonymousReturnsTrueIfTrustResolverReportsAnonymous() { - given(this.trustResolver.isAnonymous(this.user)).willReturn(true); - Assertions.assertThat(this.root.isAnonymous()).isTrue(); - } - - @Test - public void isAnonymousReturnsFalseIfTrustResolverReportsNonAnonymous() { - given(this.trustResolver.isAnonymous(this.user)).willReturn(false); - Assertions.assertThat(this.root.isAnonymous()).isFalse(); - } - - @Test - public void hasPermissionOnDomainObjectReturnsFalseIfPermissionEvaluatorDoes() { - final Object dummyDomainObject = new Object(); - final PermissionEvaluator pe = mock(PermissionEvaluator.class); - this.ctx.setVariable("domainObject", dummyDomainObject); - this.root.setPermissionEvaluator(pe); - given(pe.hasPermission(this.user, dummyDomainObject, "ignored")).willReturn(false); - Assertions.assertThat(this.root.hasPermission(dummyDomainObject, "ignored")).isFalse(); - } - - @Test - public void hasPermissionOnDomainObjectReturnsTrueIfPermissionEvaluatorDoes() { - final Object dummyDomainObject = new Object(); - final PermissionEvaluator pe = mock(PermissionEvaluator.class); - this.ctx.setVariable("domainObject", dummyDomainObject); - this.root.setPermissionEvaluator(pe); - given(pe.hasPermission(this.user, dummyDomainObject, "ignored")).willReturn(true); - Assertions.assertThat(this.root.hasPermission(dummyDomainObject, "ignored")).isTrue(); - } - - @Test - public void hasPermissionOnDomainObjectWorksWithIntegerExpressions() { - final Object dummyDomainObject = new Object(); - this.ctx.setVariable("domainObject", dummyDomainObject); - final PermissionEvaluator pe = mock(PermissionEvaluator.class); - this.root.setPermissionEvaluator(pe); - given(pe.hasPermission(eq(this.user), eq(dummyDomainObject), any(Integer.class))).willReturn(true, true, false); - Expression e = this.parser.parseExpression("hasPermission(#domainObject, 0xA)"); - // evaluator returns true - Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue(); - e = this.parser.parseExpression("hasPermission(#domainObject, 10)"); - // evaluator returns true - Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue(); - e = this.parser.parseExpression("hasPermission(#domainObject, 0xFF)"); - // evaluator returns false, make sure return value matches - Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isFalse(); - } - - @Test - public void hasPermissionWorksWithThisObject() { - Object targetObject = new Object() { - public String getX() { - return "x"; - } - }; - this.root.setThis(targetObject); - Integer i = 2; - PermissionEvaluator pe = mock(PermissionEvaluator.class); - this.root.setPermissionEvaluator(pe); - given(pe.hasPermission(this.user, targetObject, i)).willReturn(true, false); - given(pe.hasPermission(this.user, "x", i)).willReturn(true); - Expression e = this.parser.parseExpression("hasPermission(this, 2)"); - Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue(); - e = this.parser.parseExpression("hasPermission(this, 2)"); - Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isFalse(); - e = this.parser.parseExpression("hasPermission(this.x, 2)"); - Assertions.assertThat(ExpressionUtils.evaluateAsBoolean(e, this.ctx)).isTrue(); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/expression/method/PrePostAnnotationSecurityMetadataSourceTests.java b/access/src/test/java/org/springframework/security/access/expression/method/PrePostAnnotationSecurityMetadataSourceTests.java deleted file mode 100644 index d25e9b75fac..00000000000 --- a/access/src/test/java/org/springframework/security/access/expression/method/PrePostAnnotationSecurityMetadataSourceTests.java +++ /dev/null @@ -1,319 +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.access.expression.method; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.Collection; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.expression.Expression; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.annotation.sec2150.MethodInvocationFactory; -import org.springframework.security.access.intercept.method.MockMethodInvocation; -import org.springframework.security.access.prepost.PostAuthorize; -import org.springframework.security.access.prepost.PostFilter; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.access.prepost.PreFilter; -import org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource; -import org.springframework.test.util.ReflectionTestUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Luke Taylor - * @since 3.0 - */ -public class PrePostAnnotationSecurityMetadataSourceTests { - - private PrePostAnnotationSecurityMetadataSource mds = new PrePostAnnotationSecurityMetadataSource( - new ExpressionBasedAnnotationAttributeFactory(new DefaultMethodSecurityExpressionHandler())); - - private MockMethodInvocation voidImpl1; - - private MockMethodInvocation voidImpl2; - - private MockMethodInvocation voidImpl3; - - private MockMethodInvocation listImpl1; - - private MockMethodInvocation notherListImpl1; - - private MockMethodInvocation notherListImpl2; - - private MockMethodInvocation annotatedAtClassLevel; - - private MockMethodInvocation annotatedAtInterfaceLevel; - - private MockMethodInvocation annotatedAtMethodLevel; - - @BeforeEach - public void setUpData() throws Exception { - this.voidImpl1 = new MockMethodInvocation(new ReturnVoidImpl1(), ReturnVoid.class, "doSomething", List.class); - this.voidImpl2 = new MockMethodInvocation(new ReturnVoidImpl2(), ReturnVoid.class, "doSomething", List.class); - this.voidImpl3 = new MockMethodInvocation(new ReturnVoidImpl3(), ReturnVoid.class, "doSomething", List.class); - this.listImpl1 = new MockMethodInvocation(new ReturnAListImpl1(), ReturnAList.class, "doSomething", List.class); - this.notherListImpl1 = new MockMethodInvocation(new ReturnAnotherListImpl1(), ReturnAnotherList.class, - "doSomething", List.class); - this.notherListImpl2 = new MockMethodInvocation(new ReturnAnotherListImpl2(), ReturnAnotherList.class, - "doSomething", List.class); - this.annotatedAtClassLevel = new MockMethodInvocation(new CustomAnnotationAtClassLevel(), ReturnVoid.class, - "doSomething", List.class); - this.annotatedAtInterfaceLevel = new MockMethodInvocation(new CustomAnnotationAtInterfaceLevel(), - ReturnVoid2.class, "doSomething", List.class); - this.annotatedAtMethodLevel = new MockMethodInvocation(new CustomAnnotationAtMethodLevel(), ReturnVoid.class, - "doSomething", List.class); - } - - @Test - public void classLevelPreAnnotationIsPickedUpWhenNoMethodLevelExists() { - ConfigAttribute[] attrs = this.mds.getAttributes(this.voidImpl1).toArray(new ConfigAttribute[0]); - assertThat(attrs).hasSize(1); - assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue(); - PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0]; - assertThat(pre.getAuthorizeExpression()).isNotNull(); - assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("someExpression"); - assertThat(pre.getFilterExpression()).isNull(); - } - - @Test - public void mixedClassAndMethodPreAnnotationsAreBothIncluded() { - ConfigAttribute[] attrs = this.mds.getAttributes(this.voidImpl2).toArray(new ConfigAttribute[0]); - assertThat(attrs).hasSize(1); - assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue(); - PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0]; - assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("someExpression"); - assertThat(pre.getFilterExpression()).isNotNull(); - assertThat(pre.getFilterExpression().getExpressionString()).isEqualTo("somePreFilterExpression"); - } - - @Test - public void methodWithPreFilterOnlyIsAllowed() { - ConfigAttribute[] attrs = this.mds.getAttributes(this.voidImpl3).toArray(new ConfigAttribute[0]); - assertThat(attrs).hasSize(1); - assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue(); - PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0]; - assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("permitAll"); - assertThat(pre.getFilterExpression()).isNotNull(); - assertThat(pre.getFilterExpression().getExpressionString()).isEqualTo("somePreFilterExpression"); - } - - @Test - public void methodWithPostFilterOnlyIsAllowed() { - ConfigAttribute[] attrs = this.mds.getAttributes(this.listImpl1).toArray(new ConfigAttribute[0]); - assertThat(attrs).hasSize(2); - assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue(); - assertThat(attrs[1] instanceof PostInvocationExpressionAttribute).isTrue(); - PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0]; - PostInvocationExpressionAttribute post = (PostInvocationExpressionAttribute) attrs[1]; - assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("permitAll"); - assertThat(post.getFilterExpression()).isNotNull(); - assertThat(post.getFilterExpression().getExpressionString()).isEqualTo("somePostFilterExpression"); - } - - @Test - public void interfaceAttributesAreIncluded() { - ConfigAttribute[] attrs = this.mds.getAttributes(this.notherListImpl1).toArray(new ConfigAttribute[0]); - assertThat(attrs).hasSize(1); - assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue(); - PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0]; - assertThat(pre.getFilterExpression()).isNotNull(); - assertThat(pre.getAuthorizeExpression()).isNotNull(); - assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("interfaceMethodAuthzExpression"); - assertThat(pre.getFilterExpression().getExpressionString()).isEqualTo("interfacePreFilterExpression"); - } - - @Test - public void classAttributesTakesPrecedeceOverInterfaceAttributes() { - ConfigAttribute[] attrs = this.mds.getAttributes(this.notherListImpl2).toArray(new ConfigAttribute[0]); - assertThat(attrs).hasSize(1); - assertThat(attrs[0] instanceof PreInvocationExpressionAttribute).isTrue(); - PreInvocationExpressionAttribute pre = (PreInvocationExpressionAttribute) attrs[0]; - assertThat(pre.getFilterExpression()).isNotNull(); - assertThat(pre.getAuthorizeExpression()).isNotNull(); - assertThat(pre.getAuthorizeExpression().getExpressionString()).isEqualTo("interfaceMethodAuthzExpression"); - assertThat(pre.getFilterExpression().getExpressionString()).isEqualTo("classMethodPreFilterExpression"); - } - - @Test - public void customAnnotationAtClassLevelIsDetected() { - ConfigAttribute[] attrs = this.mds.getAttributes(this.annotatedAtClassLevel).toArray(new ConfigAttribute[0]); - assertThat(attrs).hasSize(1); - } - - @Test - public void customAnnotationAtInterfaceLevelIsDetected() { - ConfigAttribute[] attrs = this.mds.getAttributes(this.annotatedAtInterfaceLevel) - .toArray(new ConfigAttribute[0]); - assertThat(attrs).hasSize(1); - } - - @Test - public void customAnnotationAtMethodLevelIsDetected() { - ConfigAttribute[] attrs = this.mds.getAttributes(this.annotatedAtMethodLevel).toArray(new ConfigAttribute[0]); - assertThat(attrs).hasSize(1); - } - - @Test - public void proxyFactoryInterfaceAttributesFound() throws Exception { - MockMethodInvocation mi = MethodInvocationFactory.createSec2150MethodInvocation(); - Collection attributes = this.mds.getAttributes(mi); - assertThat(attributes).hasSize(1); - Expression expression = (Expression) ReflectionTestUtils.getField(attributes.iterator().next(), - "authorizeExpression"); - assertThat(expression.getExpressionString()).isEqualTo("hasRole('ROLE_PERSON')"); - } - - public interface ReturnVoid { - - void doSomething(List param); - - } - - public interface ReturnAList { - - List doSomething(List param); - - } - - @PreAuthorize("interfaceAuthzExpression") - public interface ReturnAnotherList { - - @PreAuthorize("interfaceMethodAuthzExpression") - @PreFilter(filterTarget = "param", value = "interfacePreFilterExpression") - List doSomething(List param); - - } - - @PreAuthorize("someExpression") - public static class ReturnVoidImpl1 implements ReturnVoid { - - @Override - public void doSomething(List param) { - } - - } - - @PreAuthorize("someExpression") - public static class ReturnVoidImpl2 implements ReturnVoid { - - @Override - @PreFilter(filterTarget = "param", value = "somePreFilterExpression") - public void doSomething(List param) { - } - - } - - public static class ReturnVoidImpl3 implements ReturnVoid { - - @Override - @PreFilter(filterTarget = "param", value = "somePreFilterExpression") - public void doSomething(List param) { - } - - } - - public static class ReturnAListImpl1 implements ReturnAList { - - @Override - @PostFilter("somePostFilterExpression") - public List doSomething(List param) { - return param; - } - - } - - public static class ReturnAListImpl2 implements ReturnAList { - - @Override - @PreAuthorize("someExpression") - @PreFilter(filterTarget = "param", value = "somePreFilterExpression") - @PostFilter("somePostFilterExpression") - @PostAuthorize("somePostAuthorizeExpression") - public List doSomething(List param) { - return param; - } - - } - - public static class ReturnAnotherListImpl1 implements ReturnAnotherList { - - @Override - public List doSomething(List param) { - return param; - } - - } - - public static class ReturnAnotherListImpl2 implements ReturnAnotherList { - - @Override - @PreFilter(filterTarget = "param", value = "classMethodPreFilterExpression") - public List doSomething(List param) { - return param; - } - - } - - @Target({ ElementType.METHOD, ElementType.TYPE }) - @Retention(RetentionPolicy.RUNTIME) - @Inherited - @PreAuthorize("customAnnotationExpression") - public @interface CustomAnnotation { - - } - - @CustomAnnotation - public interface ReturnVoid2 { - - void doSomething(List param); - - } - - @CustomAnnotation - public static class CustomAnnotationAtClassLevel implements ReturnVoid { - - @Override - public void doSomething(List param) { - } - - } - - public static class CustomAnnotationAtInterfaceLevel implements ReturnVoid2 { - - @Override - public void doSomething(List param) { - } - - } - - public static class CustomAnnotationAtMethodLevel implements ReturnVoid { - - @Override - @CustomAnnotation - public void doSomething(List param) { - } - - } - -} diff --git a/access/src/test/java/org/springframework/security/access/expression/method/SecurityRules.java b/access/src/test/java/org/springframework/security/access/expression/method/SecurityRules.java deleted file mode 100644 index 78cba692bf3..00000000000 --- a/access/src/test/java/org/springframework/security/access/expression/method/SecurityRules.java +++ /dev/null @@ -1,36 +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.access.expression.method; - -public final class SecurityRules { - - private SecurityRules() { - } - - public static boolean disallow() { - return false; - } - - public static boolean allow() { - return false; - } - - public static boolean isJoe(String s) { - return "joe".equals(s); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/intercept/AbstractSecurityInterceptorTests.java b/access/src/test/java/org/springframework/security/access/intercept/AbstractSecurityInterceptorTests.java deleted file mode 100644 index 84f38584f6f..00000000000 --- a/access/src/test/java/org/springframework/security/access/intercept/AbstractSecurityInterceptorTests.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.AccessDecisionManager; -import org.springframework.security.access.SecurityMetadataSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.util.SimpleMethodInvocation; - -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.Mockito.mock; - -/** - * Tests some {@link AbstractSecurityInterceptor} methods. Most of the testing for this - * class is found in the {@code MethodSecurityInterceptorTests} class. - * - * @author Ben Alex - */ -public class AbstractSecurityInterceptorTests { - - @Test - public void detectsIfInvocationPassedIncompatibleSecureObject() { - MockSecurityInterceptorWhichOnlySupportsStrings si = new MockSecurityInterceptorWhichOnlySupportsStrings(); - si.setRunAsManager(mock(RunAsManager.class)); - si.setAuthenticationManager(mock(AuthenticationManager.class)); - si.setAfterInvocationManager(mock(AfterInvocationManager.class)); - si.setAccessDecisionManager(mock(AccessDecisionManager.class)); - si.setSecurityMetadataSource(mock(SecurityMetadataSource.class)); - assertThatIllegalArgumentException().isThrownBy(() -> si.beforeInvocation(new SimpleMethodInvocation())); - } - - @Test - public void detectsViolationOfGetSecureObjectClassMethod() throws Exception { - MockSecurityInterceptorReturnsNull si = new MockSecurityInterceptorReturnsNull(); - si.setRunAsManager(mock(RunAsManager.class)); - si.setAuthenticationManager(mock(AuthenticationManager.class)); - si.setAfterInvocationManager(mock(AfterInvocationManager.class)); - si.setAccessDecisionManager(mock(AccessDecisionManager.class)); - si.setSecurityMetadataSource(mock(SecurityMetadataSource.class)); - assertThatIllegalArgumentException().isThrownBy(si::afterPropertiesSet); - } - - private class MockSecurityInterceptorReturnsNull extends AbstractSecurityInterceptor { - - private SecurityMetadataSource securityMetadataSource; - - @Override - public Class getSecureObjectClass() { - return null; - } - - @Override - public SecurityMetadataSource obtainSecurityMetadataSource() { - return this.securityMetadataSource; - } - - void setSecurityMetadataSource(SecurityMetadataSource securityMetadataSource) { - this.securityMetadataSource = securityMetadataSource; - } - - } - - private class MockSecurityInterceptorWhichOnlySupportsStrings extends AbstractSecurityInterceptor { - - private SecurityMetadataSource securityMetadataSource; - - @Override - public Class getSecureObjectClass() { - return String.class; - } - - @Override - public SecurityMetadataSource obtainSecurityMetadataSource() { - return this.securityMetadataSource; - } - - void setSecurityMetadataSource(SecurityMetadataSource securityMetadataSource) { - this.securityMetadataSource = securityMetadataSource; - } - - } - -} diff --git a/access/src/test/java/org/springframework/security/access/intercept/AfterInvocationProviderManagerTests.java b/access/src/test/java/org/springframework/security/access/intercept/AfterInvocationProviderManagerTests.java deleted file mode 100644 index aba3efe7181..00000000000 --- a/access/src/test/java/org/springframework/security/access/intercept/AfterInvocationProviderManagerTests.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import java.util.Collection; -import java.util.List; -import java.util.Vector; - -import org.aopalliance.intercept.MethodInvocation; -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.AfterInvocationProvider; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.core.Authentication; -import org.springframework.security.util.SimpleMethodInvocation; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests {@link AfterInvocationProviderManager}. - * - * @author Ben Alex - */ -@SuppressWarnings("unchecked") -public class AfterInvocationProviderManagerTests { - - @Test - public void testCorrectOperation() throws Exception { - AfterInvocationProviderManager manager = new AfterInvocationProviderManager(); - List list = new Vector(); - list.add(new MockAfterInvocationProvider("swap1", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP1"))); - list.add(new MockAfterInvocationProvider("swap2", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP2"))); - list.add(new MockAfterInvocationProvider("swap3", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP3"))); - manager.setProviders(list); - assertThat(manager.getProviders()).isEqualTo(list); - manager.afterPropertiesSet(); - List attr1 = SecurityConfig.createList(new String[] { "GIVE_ME_SWAP1" }); - List attr2 = SecurityConfig.createList(new String[] { "GIVE_ME_SWAP2" }); - List attr3 = SecurityConfig.createList(new String[] { "GIVE_ME_SWAP3" }); - List attr2and3 = SecurityConfig.createList(new String[] { "GIVE_ME_SWAP2", "GIVE_ME_SWAP3" }); - List attr4 = SecurityConfig.createList(new String[] { "NEVER_CAUSES_SWAP" }); - assertThat(manager.decide(null, new SimpleMethodInvocation(), attr1, "content-before-swapping")) - .isEqualTo("swap1"); - assertThat(manager.decide(null, new SimpleMethodInvocation(), attr2, "content-before-swapping")) - .isEqualTo("swap2"); - assertThat(manager.decide(null, new SimpleMethodInvocation(), attr3, "content-before-swapping")) - .isEqualTo("swap3"); - assertThat(manager.decide(null, new SimpleMethodInvocation(), attr4, "content-before-swapping")) - .isEqualTo("content-before-swapping"); - assertThat(manager.decide(null, new SimpleMethodInvocation(), attr2and3, "content-before-swapping")) - .isEqualTo("swap3"); - } - - @Test - public void testRejectsEmptyProvidersList() { - AfterInvocationProviderManager manager = new AfterInvocationProviderManager(); - List list = new Vector(); - assertThatIllegalArgumentException().isThrownBy(() -> manager.setProviders(list)); - } - - @Test - public void testRejectsNonAfterInvocationProviders() { - AfterInvocationProviderManager manager = new AfterInvocationProviderManager(); - List list = new Vector(); - list.add(new MockAfterInvocationProvider("swap1", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP1"))); - list.add(45); - list.add(new MockAfterInvocationProvider("swap3", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP3"))); - assertThatIllegalArgumentException().isThrownBy(() -> manager.setProviders(list)); - } - - @Test - public void testRejectsNullProvidersList() throws Exception { - AfterInvocationProviderManager manager = new AfterInvocationProviderManager(); - assertThatIllegalArgumentException().isThrownBy(manager::afterPropertiesSet); - } - - @Test - public void testSupportsConfigAttributeIteration() throws Exception { - AfterInvocationProviderManager manager = new AfterInvocationProviderManager(); - List list = new Vector(); - list.add(new MockAfterInvocationProvider("swap1", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP1"))); - list.add(new MockAfterInvocationProvider("swap2", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP2"))); - list.add(new MockAfterInvocationProvider("swap3", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP3"))); - manager.setProviders(list); - manager.afterPropertiesSet(); - assertThat(manager.supports(new SecurityConfig("UNKNOWN_ATTRIB"))).isFalse(); - assertThat(manager.supports(new SecurityConfig("GIVE_ME_SWAP2"))).isTrue(); - } - - @Test - public void testSupportsSecureObjectIteration() throws Exception { - AfterInvocationProviderManager manager = new AfterInvocationProviderManager(); - List list = new Vector(); - list.add(new MockAfterInvocationProvider("swap1", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP1"))); - list.add(new MockAfterInvocationProvider("swap2", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP2"))); - list.add(new MockAfterInvocationProvider("swap3", MethodInvocation.class, new SecurityConfig("GIVE_ME_SWAP3"))); - manager.setProviders(list); - manager.afterPropertiesSet(); - // assertFalse(manager.supports(FilterInvocation.class)); - assertThat(manager.supports(MethodInvocation.class)).isTrue(); - } - - /** - * Always returns the constructor-defined forceReturnObject, provided the - * same configuration attribute was provided. Also stores the secure object it - * supports. - */ - private class MockAfterInvocationProvider implements AfterInvocationProvider { - - private Class secureObject; - - private ConfigAttribute configAttribute; - - private Object forceReturnObject; - - MockAfterInvocationProvider(Object forceReturnObject, Class secureObject, ConfigAttribute configAttribute) { - this.forceReturnObject = forceReturnObject; - this.secureObject = secureObject; - this.configAttribute = configAttribute; - } - - @Override - public Object decide(Authentication authentication, Object object, Collection config, - Object returnedObject) throws AccessDeniedException { - if (config.contains(this.configAttribute)) { - return this.forceReturnObject; - } - return returnedObject; - } - - @Override - public boolean supports(Class clazz) { - return this.secureObject.isAssignableFrom(clazz); - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return attribute.equals(this.configAttribute); - } - - } - -} diff --git a/access/src/test/java/org/springframework/security/access/intercept/InterceptorStatusTokenTests.java b/access/src/test/java/org/springframework/security/access/intercept/InterceptorStatusTokenTests.java deleted file mode 100644 index f1a6f1f60cb..00000000000 --- a/access/src/test/java/org/springframework/security/access/intercept/InterceptorStatusTokenTests.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import java.util.List; - -import org.aopalliance.intercept.MethodInvocation; -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.util.SimpleMethodInvocation; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests {@link InterceptorStatusToken}. - * - * @author Ben Alex - */ -public class InterceptorStatusTokenTests { - - @Test - public void testOperation() { - List attr = SecurityConfig.createList("FOO"); - MethodInvocation mi = new SimpleMethodInvocation(); - SecurityContext ctx = SecurityContextHolder.createEmptyContext(); - InterceptorStatusToken token = new InterceptorStatusToken(ctx, true, attr, mi); - assertThat(token.isContextHolderRefreshRequired()).isTrue(); - assertThat(token.getAttributes()).isEqualTo(attr); - assertThat(token.getSecureObject()).isEqualTo(mi); - assertThat(token.getSecurityContext()).isSameAs(ctx); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/intercept/NullRunAsManagerTests.java b/access/src/test/java/org/springframework/security/access/intercept/NullRunAsManagerTests.java deleted file mode 100644 index 8ec6d3514f1..00000000000 --- a/access/src/test/java/org/springframework/security/access/intercept/NullRunAsManagerTests.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.SecurityConfig; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests {@link NullRunAsManager}. - * - * @author Ben Alex - */ -public class NullRunAsManagerTests { - - @Test - public void testAlwaysReturnsNull() { - NullRunAsManager runAs = new NullRunAsManager(); - assertThat(runAs.buildRunAs(null, null, null)).isNull(); - } - - @Test - public void testAlwaysSupportsClass() { - NullRunAsManager runAs = new NullRunAsManager(); - assertThat(runAs.supports(String.class)).isTrue(); - } - - @Test - public void testNeverSupportsAttribute() { - NullRunAsManager runAs = new NullRunAsManager(); - assertThat(runAs.supports(new SecurityConfig("X"))).isFalse(); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/intercept/RunAsImplAuthenticationProviderTests.java b/access/src/test/java/org/springframework/security/access/intercept/RunAsImplAuthenticationProviderTests.java deleted file mode 100644 index 91a616415b4..00000000000 --- a/access/src/test/java/org/springframework/security/access/intercept/RunAsImplAuthenticationProviderTests.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests {@link RunAsImplAuthenticationProvider}. - */ -public class RunAsImplAuthenticationProviderTests { - - @Test - public void testAuthenticationFailDueToWrongKey() { - RunAsUserToken token = new RunAsUserToken("wrong_key", "Test", "Password", - AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), UsernamePasswordAuthenticationToken.class); - RunAsImplAuthenticationProvider provider = new RunAsImplAuthenticationProvider(); - provider.setKey("hello_world"); - assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> provider.authenticate(token)); - } - - @Test - public void testAuthenticationSuccess() { - RunAsUserToken token = new RunAsUserToken("my_password", "Test", "Password", - AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), UsernamePasswordAuthenticationToken.class); - RunAsImplAuthenticationProvider provider = new RunAsImplAuthenticationProvider(); - provider.setKey("my_password"); - Authentication result = provider.authenticate(token); - assertThat(result instanceof RunAsUserToken).as("Should have returned RunAsUserToken").isTrue(); - RunAsUserToken resultCast = (RunAsUserToken) result; - assertThat(resultCast.getKeyHash()).isEqualTo("my_password".hashCode()); - } - - @Test - public void testStartupFailsIfNoKey() throws Exception { - RunAsImplAuthenticationProvider provider = new RunAsImplAuthenticationProvider(); - assertThatIllegalArgumentException().isThrownBy(provider::afterPropertiesSet); - } - - @Test - public void testStartupSuccess() throws Exception { - RunAsImplAuthenticationProvider provider = new RunAsImplAuthenticationProvider(); - provider.setKey("hello_world"); - assertThat(provider.getKey()).isEqualTo("hello_world"); - provider.afterPropertiesSet(); - } - - @Test - public void testSupports() { - RunAsImplAuthenticationProvider provider = new RunAsImplAuthenticationProvider(); - assertThat(provider.supports(RunAsUserToken.class)).isTrue(); - assertThat(!provider.supports(TestingAuthenticationToken.class)).isTrue(); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/intercept/RunAsManagerImplTests.java b/access/src/test/java/org/springframework/security/access/intercept/RunAsManagerImplTests.java deleted file mode 100644 index 16dc6a5c77a..00000000000 --- a/access/src/test/java/org/springframework/security/access/intercept/RunAsManagerImplTests.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import java.util.Set; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.fail; - -/** - * Tests {@link RunAsManagerImpl}. - * - * @author Ben Alex - */ -public class RunAsManagerImplTests { - - @Test - public void testAlwaysSupportsClass() { - RunAsManagerImpl runAs = new RunAsManagerImpl(); - assertThat(runAs.supports(String.class)).isTrue(); - } - - @Test - public void testDoesNotReturnAdditionalAuthoritiesIfCalledWithoutARunAsSetting() { - UsernamePasswordAuthenticationToken inputToken = UsernamePasswordAuthenticationToken.authenticated("Test", - "Password", AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO")); - RunAsManagerImpl runAs = new RunAsManagerImpl(); - runAs.setKey("my_password"); - Authentication resultingToken = runAs.buildRunAs(inputToken, new Object(), - SecurityConfig.createList("SOMETHING_WE_IGNORE")); - assertThat(resultingToken).isNull(); - } - - @Test - public void testRespectsRolePrefix() { - UsernamePasswordAuthenticationToken inputToken = UsernamePasswordAuthenticationToken.authenticated("Test", - "Password", AuthorityUtils.createAuthorityList("ONE", "TWO")); - RunAsManagerImpl runAs = new RunAsManagerImpl(); - runAs.setKey("my_password"); - runAs.setRolePrefix("FOOBAR_"); - Authentication result = runAs.buildRunAs(inputToken, new Object(), - SecurityConfig.createList("RUN_AS_SOMETHING")); - assertThat(result instanceof RunAsUserToken).withFailMessage("Should have returned a RunAsUserToken").isTrue(); - assertThat(result.getPrincipal()).isEqualTo(inputToken.getPrincipal()); - assertThat(result.getCredentials()).isEqualTo(inputToken.getCredentials()); - Set authorities = AuthorityUtils.authorityListToSet(result.getAuthorities()); - assertThat(authorities).contains("FOOBAR_RUN_AS_SOMETHING"); - assertThat(authorities).contains("ONE"); - assertThat(authorities).contains("TWO"); - RunAsUserToken resultCast = (RunAsUserToken) result; - assertThat(resultCast.getKeyHash()).isEqualTo("my_password".hashCode()); - } - - @Test - public void testReturnsAdditionalGrantedAuthorities() { - UsernamePasswordAuthenticationToken inputToken = UsernamePasswordAuthenticationToken.authenticated("Test", - "Password", AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO")); - RunAsManagerImpl runAs = new RunAsManagerImpl(); - runAs.setKey("my_password"); - Authentication result = runAs.buildRunAs(inputToken, new Object(), - SecurityConfig.createList("RUN_AS_SOMETHING")); - if (!(result instanceof RunAsUserToken)) { - fail("Should have returned a RunAsUserToken"); - } - assertThat(result.getPrincipal()).isEqualTo(inputToken.getPrincipal()); - assertThat(result.getCredentials()).isEqualTo(inputToken.getCredentials()); - Set authorities = AuthorityUtils.authorityListToSet(result.getAuthorities()); - assertThat(authorities).contains("ROLE_RUN_AS_SOMETHING"); - assertThat(authorities).contains("ROLE_ONE"); - assertThat(authorities).contains("ROLE_TWO"); - RunAsUserToken resultCast = (RunAsUserToken) result; - assertThat(resultCast.getKeyHash()).isEqualTo("my_password".hashCode()); - } - - @Test - public void testStartupDetectsMissingKey() throws Exception { - RunAsManagerImpl runAs = new RunAsManagerImpl(); - assertThatIllegalArgumentException().isThrownBy(runAs::afterPropertiesSet); - } - - @Test - public void testStartupSuccessfulWithKey() throws Exception { - RunAsManagerImpl runAs = new RunAsManagerImpl(); - runAs.setKey("hello_world"); - runAs.afterPropertiesSet(); - assertThat(runAs.getKey()).isEqualTo("hello_world"); - } - - @Test - public void testSupports() { - RunAsManager runAs = new RunAsManagerImpl(); - assertThat(runAs.supports(new SecurityConfig("RUN_AS_SOMETHING"))).isTrue(); - assertThat(!runAs.supports(new SecurityConfig("ROLE_WHICH_IS_IGNORED"))).isTrue(); - assertThat(!runAs.supports(new SecurityConfig("role_LOWER_CASE_FAILS"))).isTrue(); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/intercept/RunAsUserTokenTests.java b/access/src/test/java/org/springframework/security/access/intercept/RunAsUserTokenTests.java deleted file mode 100644 index 35c1cde5ea1..00000000000 --- a/access/src/test/java/org/springframework/security/access/intercept/RunAsUserTokenTests.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.authority.AuthorityUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests {@link RunAsUserToken}. - * - * @author Ben Alex - */ -public class RunAsUserTokenTests { - - @Test - public void testAuthenticationSetting() { - RunAsUserToken token = new RunAsUserToken("my_password", "Test", "Password", - AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), UsernamePasswordAuthenticationToken.class); - assertThat(token.isAuthenticated()).isTrue(); - token.setAuthenticated(false); - assertThat(!token.isAuthenticated()).isTrue(); - } - - @Test - public void testGetters() { - RunAsUserToken token = new RunAsUserToken("my_password", "Test", "Password", - AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), UsernamePasswordAuthenticationToken.class); - assertThat("Test").isEqualTo(token.getPrincipal()); - assertThat("Password").isEqualTo(token.getCredentials()); - assertThat("my_password".hashCode()).isEqualTo(token.getKeyHash()); - assertThat(UsernamePasswordAuthenticationToken.class).isEqualTo(token.getOriginalAuthentication()); - } - - @Test - public void testNoArgConstructorDoesntExist() { - assertThatExceptionOfType(NoSuchMethodException.class) - .isThrownBy(() -> RunAsUserToken.class.getDeclaredConstructor((Class[]) null)); - } - - @Test - public void testToString() { - RunAsUserToken token = new RunAsUserToken("my_password", "Test", "Password", - AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), UsernamePasswordAuthenticationToken.class); - assertThat(token.toString() - .lastIndexOf("Original Class: " + UsernamePasswordAuthenticationToken.class.getName().toString()) != -1) - .isTrue(); - } - - // SEC-1792 - @Test - public void testToStringNullOriginalAuthentication() { - RunAsUserToken token = new RunAsUserToken("my_password", "Test", "Password", - AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), null); - assertThat(token.toString().lastIndexOf("Original Class: null") != -1).isTrue(); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityInterceptorTests.java b/access/src/test/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityInterceptorTests.java deleted file mode 100644 index 9b0ca1577d9..00000000000 --- a/access/src/test/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityInterceptorTests.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept.aopalliance; - -import java.util.List; - -import org.aopalliance.intercept.MethodInvocation; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.security.access.AccessDecisionManager; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.ITargetObject; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.access.TargetObject; -import org.springframework.security.access.event.AuthorizationFailureEvent; -import org.springframework.security.access.event.AuthorizedEvent; -import org.springframework.security.access.intercept.AfterInvocationManager; -import org.springframework.security.access.intercept.RunAsManager; -import org.springframework.security.access.intercept.RunAsUserToken; -import org.springframework.security.access.method.MethodSecurityMetadataSource; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -/** - * Tests {@link MethodSecurityInterceptor}. - * - * @author Ben Alex - * @author Rob Winch - */ -@SuppressWarnings("unchecked") -public class MethodSecurityInterceptorTests { - - private TestingAuthenticationToken token; - - private MethodSecurityInterceptor interceptor; - - private ITargetObject realTarget; - - private ITargetObject advisedTarget; - - private AccessDecisionManager adm; - - private MethodSecurityMetadataSource mds; - - private AuthenticationManager authman; - - private ApplicationEventPublisher eventPublisher; - - @BeforeEach - public final void setUp() { - SecurityContextHolder.clearContext(); - this.token = new TestingAuthenticationToken("Test", "Password"); - this.interceptor = new MethodSecurityInterceptor(); - this.adm = mock(AccessDecisionManager.class); - this.authman = mock(AuthenticationManager.class); - this.mds = mock(MethodSecurityMetadataSource.class); - this.eventPublisher = mock(ApplicationEventPublisher.class); - this.interceptor.setAccessDecisionManager(this.adm); - this.interceptor.setAuthenticationManager(this.authman); - this.interceptor.setSecurityMetadataSource(this.mds); - this.interceptor.setApplicationEventPublisher(this.eventPublisher); - createTarget(false); - } - - @AfterEach - public void tearDown() { - SecurityContextHolder.clearContext(); - } - - private void createTarget(boolean useMock) { - this.realTarget = useMock ? mock(ITargetObject.class) : new TargetObject(); - ProxyFactory pf = new ProxyFactory(this.realTarget); - pf.addAdvice(this.interceptor); - this.advisedTarget = (ITargetObject) pf.getProxy(); - } - - @Test - public void gettersReturnExpectedData() { - RunAsManager runAs = mock(RunAsManager.class); - AfterInvocationManager aim = mock(AfterInvocationManager.class); - this.interceptor.setRunAsManager(runAs); - this.interceptor.setAfterInvocationManager(aim); - assertThat(this.interceptor.getAccessDecisionManager()).isEqualTo(this.adm); - assertThat(this.interceptor.getRunAsManager()).isEqualTo(runAs); - assertThat(this.interceptor.getAuthenticationManager()).isEqualTo(this.authman); - assertThat(this.interceptor.getSecurityMetadataSource()).isEqualTo(this.mds); - assertThat(this.interceptor.getAfterInvocationManager()).isEqualTo(aim); - } - - @Test - public void missingAccessDecisionManagerIsDetected() throws Exception { - this.interceptor.setAccessDecisionManager(null); - assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); - } - - @Test - public void missingAuthenticationManagerIsDetected() throws Exception { - this.interceptor.setAuthenticationManager(null); - assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); - } - - @Test - public void missingMethodSecurityMetadataSourceIsRejected() throws Exception { - this.interceptor.setSecurityMetadataSource(null); - assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); - } - - @Test - public void missingRunAsManagerIsRejected() throws Exception { - this.interceptor.setRunAsManager(null); - assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); - } - - @Test - public void initializationRejectsSecurityMetadataSourceThatDoesNotSupportMethodInvocation() throws Throwable { - given(this.mds.supports(MethodInvocation.class)).willReturn(false); - assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); - } - - @Test - public void initializationRejectsAccessDecisionManagerThatDoesNotSupportMethodInvocation() throws Exception { - given(this.mds.supports(MethodInvocation.class)).willReturn(true); - given(this.adm.supports(MethodInvocation.class)).willReturn(false); - assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); - } - - @Test - public void intitalizationRejectsRunAsManagerThatDoesNotSupportMethodInvocation() throws Exception { - final RunAsManager ram = mock(RunAsManager.class); - given(ram.supports(MethodInvocation.class)).willReturn(false); - this.interceptor.setRunAsManager(ram); - assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); - } - - @Test - public void intitalizationRejectsAfterInvocationManagerThatDoesNotSupportMethodInvocation() throws Exception { - final AfterInvocationManager aim = mock(AfterInvocationManager.class); - given(aim.supports(MethodInvocation.class)).willReturn(false); - this.interceptor.setAfterInvocationManager(aim); - assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); - } - - @Test - public void initializationFailsIfAccessDecisionManagerRejectsConfigAttributes() throws Exception { - given(this.adm.supports(any(ConfigAttribute.class))).willReturn(false); - assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.afterPropertiesSet()); - } - - @Test - public void validationNotAttemptedIfIsValidateConfigAttributesSetToFalse() throws Exception { - given(this.adm.supports(MethodInvocation.class)).willReturn(true); - given(this.mds.supports(MethodInvocation.class)).willReturn(true); - this.interceptor.setValidateConfigAttributes(false); - this.interceptor.afterPropertiesSet(); - verify(this.mds, never()).getAllConfigAttributes(); - verify(this.adm, never()).supports(any(ConfigAttribute.class)); - } - - @Test - public void validationNotAttemptedIfMethodSecurityMetadataSourceReturnsNullForAttributes() throws Exception { - given(this.adm.supports(MethodInvocation.class)).willReturn(true); - given(this.mds.supports(MethodInvocation.class)).willReturn(true); - given(this.mds.getAllConfigAttributes()).willReturn(null); - this.interceptor.setValidateConfigAttributes(true); - this.interceptor.afterPropertiesSet(); - verify(this.adm, never()).supports(any(ConfigAttribute.class)); - } - - @Test - public void callingAPublicMethodFacadeWillNotRepeatSecurityChecksWhenPassedToTheSecuredMethodItFronts() { - mdsReturnsNull(); - String result = this.advisedTarget.publicMakeLowerCase("HELLO"); - assertThat(result).isEqualTo("hello Authentication empty"); - } - - @Test - public void callingAPublicMethodWhenPresentingAnAuthenticationObjectDoesntChangeItsAuthenticatedProperty() { - mdsReturnsNull(); - SecurityContextHolder.getContext().setAuthentication(this.token); - assertThat(this.advisedTarget.publicMakeLowerCase("HELLO")) - .isEqualTo("hello org.springframework.security.authentication.TestingAuthenticationToken false"); - assertThat(!this.token.isAuthenticated()).isTrue(); - } - - @Test - public void callIsntMadeWhenAuthenticationManagerRejectsAuthentication() { - final TestingAuthenticationToken token = new TestingAuthenticationToken("Test", "Password"); - SecurityContextHolder.getContext().setAuthentication(token); - mdsReturnsUserRole(); - given(this.authman.authenticate(token)).willThrow(new BadCredentialsException("rejected")); - assertThatExceptionOfType(AuthenticationException.class) - .isThrownBy(() -> this.advisedTarget.makeLowerCase("HELLO")); - } - - @Test - public void callSucceedsIfAccessDecisionManagerGrantsAccess() { - this.token.setAuthenticated(true); - this.interceptor.setPublishAuthorizationSuccess(true); - SecurityContextHolder.getContext().setAuthentication(this.token); - mdsReturnsUserRole(); - String result = this.advisedTarget.makeLowerCase("HELLO"); - // Note we check the isAuthenticated remained true in following line - assertThat(result) - .isEqualTo("hello org.springframework.security.authentication.TestingAuthenticationToken true"); - verify(this.eventPublisher).publishEvent(any(AuthorizedEvent.class)); - } - - @Test - public void callIsntMadeWhenAccessDecisionManagerRejectsAccess() { - SecurityContextHolder.getContext().setAuthentication(this.token); - // Use mocked target to make sure invocation doesn't happen (not in expectations - // so test would fail) - createTarget(true); - mdsReturnsUserRole(); - given(this.authman.authenticate(this.token)).willReturn(this.token); - willThrow(new AccessDeniedException("rejected")).given(this.adm) - .decide(any(Authentication.class), any(MethodInvocation.class), any(List.class)); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.advisedTarget.makeUpperCase("HELLO")); - verify(this.eventPublisher).publishEvent(any(AuthorizationFailureEvent.class)); - } - - @Test - public void rejectsNullSecuredObjects() throws Throwable { - assertThatIllegalArgumentException().isThrownBy(() -> this.interceptor.invoke(null)); - } - - @Test - public void runAsReplacementIsCorrectlySet() { - SecurityContext ctx = SecurityContextHolder.getContext(); - ctx.setAuthentication(this.token); - this.token.setAuthenticated(true); - final RunAsManager runAs = mock(RunAsManager.class); - final RunAsUserToken runAsToken = new RunAsUserToken("key", "someone", "creds", this.token.getAuthorities(), - TestingAuthenticationToken.class); - this.interceptor.setRunAsManager(runAs); - mdsReturnsUserRole(); - given(runAs.buildRunAs(eq(this.token), any(MethodInvocation.class), any(List.class))).willReturn(runAsToken); - String result = this.advisedTarget.makeUpperCase("hello"); - assertThat(result).isEqualTo("HELLO org.springframework.security.access.intercept.RunAsUserToken true"); - // Check we've changed back - assertThat(SecurityContextHolder.getContext()).isSameAs(ctx); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.token); - } - - // SEC-1967 - @Test - public void runAsReplacementCleansAfterException() { - createTarget(true); - given(this.realTarget.makeUpperCase(anyString())).willThrow(new RuntimeException()); - SecurityContext ctx = SecurityContextHolder.getContext(); - ctx.setAuthentication(this.token); - this.token.setAuthenticated(true); - final RunAsManager runAs = mock(RunAsManager.class); - final RunAsUserToken runAsToken = new RunAsUserToken("key", "someone", "creds", this.token.getAuthorities(), - TestingAuthenticationToken.class); - this.interceptor.setRunAsManager(runAs); - mdsReturnsUserRole(); - given(runAs.buildRunAs(eq(this.token), any(MethodInvocation.class), any(List.class))).willReturn(runAsToken); - assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> this.advisedTarget.makeUpperCase("hello")); - // Check we've changed back - assertThat(SecurityContextHolder.getContext()).isSameAs(ctx); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.token); - } - - @Test - public void emptySecurityContextIsRejected() { - mdsReturnsUserRole(); - assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) - .isThrownBy(() -> this.advisedTarget.makeUpperCase("hello")); - } - - @Test - public void afterInvocationManagerIsNotInvokedIfExceptionIsRaised() throws Throwable { - MethodInvocation mi = mock(MethodInvocation.class); - this.token.setAuthenticated(true); - SecurityContextHolder.getContext().setAuthentication(this.token); - mdsReturnsUserRole(); - AfterInvocationManager aim = mock(AfterInvocationManager.class); - this.interceptor.setAfterInvocationManager(aim); - given(mi.proceed()).willThrow(new Throwable()); - assertThatExceptionOfType(Throwable.class).isThrownBy(() -> this.interceptor.invoke(mi)); - verifyNoMoreInteractions(aim); - } - - void mdsReturnsNull() { - given(this.mds.getAttributes(any(MethodInvocation.class))).willReturn(null); - } - - void mdsReturnsUserRole() { - given(this.mds.getAttributes(any(MethodInvocation.class))).willReturn(SecurityConfig.createList("ROLE_USER")); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityMetadataSourceAdvisorTests.java b/access/src/test/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityMetadataSourceAdvisorTests.java deleted file mode 100644 index b8e314e4231..00000000000 --- a/access/src/test/java/org/springframework/security/access/intercept/aopalliance/MethodSecurityMetadataSourceAdvisorTests.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept.aopalliance; - -import java.lang.reflect.Method; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.access.TargetObject; -import org.springframework.security.access.method.MethodSecurityMetadataSource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests {@link MethodSecurityMetadataSourceAdvisor}. - * - * @author Ben Alex - */ -public class MethodSecurityMetadataSourceAdvisorTests { - - @Test - public void testAdvisorReturnsFalseWhenMethodInvocationNotDefined() throws Exception { - Class clazz = TargetObject.class; - Method method = clazz.getMethod("makeLowerCase", new Class[] { String.class }); - MethodSecurityMetadataSource mds = mock(MethodSecurityMetadataSource.class); - given(mds.getAttributes(method, clazz)).willReturn(null); - MethodSecurityMetadataSourceAdvisor advisor = new MethodSecurityMetadataSourceAdvisor("", mds, ""); - assertThat(advisor.getPointcut().getMethodMatcher().matches(method, clazz)).isFalse(); - } - - @Test - public void testAdvisorReturnsTrueWhenMethodInvocationIsDefined() throws Exception { - Class clazz = TargetObject.class; - Method method = clazz.getMethod("countLength", new Class[] { String.class }); - MethodSecurityMetadataSource mds = mock(MethodSecurityMetadataSource.class); - given(mds.getAttributes(method, clazz)).willReturn(SecurityConfig.createList("ROLE_A")); - MethodSecurityMetadataSourceAdvisor advisor = new MethodSecurityMetadataSourceAdvisor("", mds, ""); - assertThat(advisor.getPointcut().getMethodMatcher().matches(method, clazz)).isTrue(); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/intercept/aspectj/AspectJMethodSecurityInterceptorTests.java b/access/src/test/java/org/springframework/security/access/intercept/aspectj/AspectJMethodSecurityInterceptorTests.java deleted file mode 100644 index 0c80f07e778..00000000000 --- a/access/src/test/java/org/springframework/security/access/intercept/aspectj/AspectJMethodSecurityInterceptorTests.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept.aspectj; - -import java.lang.reflect.Method; -import java.util.List; - -import org.aopalliance.intercept.MethodInvocation; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.Signature; -import org.aspectj.lang.reflect.CodeSignature; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import org.springframework.security.access.AccessDecisionManager; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.access.TargetObject; -import org.springframework.security.access.intercept.AfterInvocationManager; -import org.springframework.security.access.intercept.RunAsManager; -import org.springframework.security.access.intercept.RunAsUserToken; -import org.springframework.security.access.method.MethodSecurityMetadataSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.util.ClassUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -/** - * Tests {@link AspectJMethodSecurityInterceptor}. - * - * @author Ben Alex - * @author Luke Taylor - * @author Rob Winch - */ -public class AspectJMethodSecurityInterceptorTests { - - private TestingAuthenticationToken token; - - private AspectJMethodSecurityInterceptor interceptor; - - @Mock - private AccessDecisionManager adm; - - @Mock - private MethodSecurityMetadataSource mds; - - @Mock - private AuthenticationManager authman; - - @Mock - private AspectJCallback aspectJCallback; - - private ProceedingJoinPoint joinPoint; - - @BeforeEach - public final void setUp() { - MockitoAnnotations.initMocks(this); - SecurityContextHolder.clearContext(); - this.token = new TestingAuthenticationToken("Test", "Password"); - this.interceptor = new AspectJMethodSecurityInterceptor(); - this.interceptor.setAccessDecisionManager(this.adm); - this.interceptor.setAuthenticationManager(this.authman); - this.interceptor.setSecurityMetadataSource(this.mds); - // Set up joinpoint information for the countLength method on TargetObject - this.joinPoint = mock(ProceedingJoinPoint.class); // new MockJoinPoint(new - // TargetObject(), method); - Signature sig = mock(Signature.class); - given(sig.getDeclaringType()).willReturn(TargetObject.class); - JoinPoint.StaticPart staticPart = mock(JoinPoint.StaticPart.class); - given(this.joinPoint.getSignature()).willReturn(sig); - given(this.joinPoint.getStaticPart()).willReturn(staticPart); - CodeSignature codeSig = mock(CodeSignature.class); - given(codeSig.getName()).willReturn("countLength"); - given(codeSig.getDeclaringType()).willReturn(TargetObject.class); - given(codeSig.getParameterTypes()).willReturn(new Class[] { String.class }); - given(staticPart.getSignature()).willReturn(codeSig); - given(this.mds.getAttributes(any())).willReturn(SecurityConfig.createList("ROLE_USER")); - given(this.authman.authenticate(this.token)).willReturn(this.token); - } - - @AfterEach - public void clearContext() { - SecurityContextHolder.clearContext(); - } - - @Test - public void callbackIsInvokedWhenPermissionGranted() throws Throwable { - SecurityContextHolder.getContext().setAuthentication(this.token); - this.interceptor.invoke(this.joinPoint, this.aspectJCallback); - verify(this.aspectJCallback).proceedWithObject(); - // Just try the other method too - this.interceptor.invoke(this.joinPoint); - } - - @SuppressWarnings("unchecked") - @Test - public void callbackIsNotInvokedWhenPermissionDenied() { - willThrow(new AccessDeniedException("denied")).given(this.adm).decide(any(), any(), any()); - SecurityContextHolder.getContext().setAuthentication(this.token); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.interceptor.invoke(this.joinPoint, this.aspectJCallback)); - verify(this.aspectJCallback, never()).proceedWithObject(); - } - - @Test - public void adapterHoldsCorrectData() { - TargetObject to = new TargetObject(); - Method m = ClassUtils.getMethodIfAvailable(TargetObject.class, "countLength", new Class[] { String.class }); - given(this.joinPoint.getTarget()).willReturn(to); - given(this.joinPoint.getArgs()).willReturn(new Object[] { "Hi" }); - MethodInvocationAdapter mia = new MethodInvocationAdapter(this.joinPoint); - assertThat(mia.getArguments()[0]).isEqualTo("Hi"); - assertThat(mia.getStaticPart()).isEqualTo(m); - assertThat(mia.getMethod()).isEqualTo(m); - assertThat(mia.getThis()).isSameAs(to); - } - - @Test - public void afterInvocationManagerIsNotInvokedIfExceptionIsRaised() { - this.token.setAuthenticated(true); - SecurityContextHolder.getContext().setAuthentication(this.token); - AfterInvocationManager aim = mock(AfterInvocationManager.class); - this.interceptor.setAfterInvocationManager(aim); - given(this.aspectJCallback.proceedWithObject()).willThrow(new RuntimeException()); - assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> this.interceptor.invoke(this.joinPoint, this.aspectJCallback)); - verifyNoMoreInteractions(aim); - } - - // SEC-1967 - @Test - @SuppressWarnings("unchecked") - public void invokeWithAspectJCallbackRunAsReplacementCleansAfterException() { - SecurityContext ctx = SecurityContextHolder.getContext(); - ctx.setAuthentication(this.token); - this.token.setAuthenticated(true); - final RunAsManager runAs = mock(RunAsManager.class); - final RunAsUserToken runAsToken = new RunAsUserToken("key", "someone", "creds", this.token.getAuthorities(), - TestingAuthenticationToken.class); - this.interceptor.setRunAsManager(runAs); - given(runAs.buildRunAs(eq(this.token), any(MethodInvocation.class), any(List.class))).willReturn(runAsToken); - given(this.aspectJCallback.proceedWithObject()).willThrow(new RuntimeException()); - assertThatExceptionOfType(RuntimeException.class) - .isThrownBy(() -> this.interceptor.invoke(this.joinPoint, this.aspectJCallback)); - // Check we've changed back - assertThat(SecurityContextHolder.getContext()).isSameAs(ctx); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.token); - } - - // SEC-1967 - @Test - @SuppressWarnings("unchecked") - public void invokeRunAsReplacementCleansAfterException() throws Throwable { - SecurityContext ctx = SecurityContextHolder.getContext(); - ctx.setAuthentication(this.token); - this.token.setAuthenticated(true); - final RunAsManager runAs = mock(RunAsManager.class); - final RunAsUserToken runAsToken = new RunAsUserToken("key", "someone", "creds", this.token.getAuthorities(), - TestingAuthenticationToken.class); - this.interceptor.setRunAsManager(runAs); - given(runAs.buildRunAs(eq(this.token), any(MethodInvocation.class), any(List.class))).willReturn(runAsToken); - given(this.joinPoint.proceed()).willThrow(new RuntimeException()); - assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> this.interceptor.invoke(this.joinPoint)); - // Check we've changed back - assertThat(SecurityContextHolder.getContext()).isSameAs(ctx); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.token); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/intercept/method/MapBasedMethodSecurityMetadataSourceTests.java b/access/src/test/java/org/springframework/security/access/intercept/method/MapBasedMethodSecurityMetadataSourceTests.java deleted file mode 100644 index dc2e1dff604..00000000000 --- a/access/src/test/java/org/springframework/security/access/intercept/method/MapBasedMethodSecurityMetadataSourceTests.java +++ /dev/null @@ -1,82 +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.access.intercept.method; - -import java.lang.reflect.Method; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.access.method.MapBasedMethodSecurityMetadataSource; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link MapBasedMethodSecurityMetadataSource}. - * - * @author Luke Taylor - * @since 2.0.4 - */ -public class MapBasedMethodSecurityMetadataSourceTests { - - private final List ROLE_A = SecurityConfig.createList("ROLE_A"); - - private final List ROLE_B = SecurityConfig.createList("ROLE_B"); - - private MapBasedMethodSecurityMetadataSource mds; - - private Method someMethodString; - - private Method someMethodInteger; - - @BeforeEach - public void initialize() throws Exception { - this.mds = new MapBasedMethodSecurityMetadataSource(); - this.someMethodString = MockService.class.getMethod("someMethod", String.class); - this.someMethodInteger = MockService.class.getMethod("someMethod", Integer.class); - } - - @Test - public void wildcardedMatchIsOverwrittenByMoreSpecificMatch() { - this.mds.addSecureMethod(MockService.class, "some*", this.ROLE_A); - this.mds.addSecureMethod(MockService.class, "someMethod*", this.ROLE_B); - assertThat(this.mds.getAttributes(this.someMethodInteger, MockService.class)).isEqualTo(this.ROLE_B); - } - - @Test - public void methodsWithDifferentArgumentsAreMatchedCorrectly() { - this.mds.addSecureMethod(MockService.class, this.someMethodInteger, this.ROLE_A); - this.mds.addSecureMethod(MockService.class, this.someMethodString, this.ROLE_B); - assertThat(this.mds.getAttributes(this.someMethodInteger, MockService.class)).isEqualTo(this.ROLE_A); - assertThat(this.mds.getAttributes(this.someMethodString, MockService.class)).isEqualTo(this.ROLE_B); - } - - @SuppressWarnings("unused") - private class MockService { - - public void someMethod(String s) { - } - - public void someMethod(Integer i) { - } - - } - -} diff --git a/access/src/test/java/org/springframework/security/access/intercept/method/MethodInvocationPrivilegeEvaluatorTests.java b/access/src/test/java/org/springframework/security/access/intercept/method/MethodInvocationPrivilegeEvaluatorTests.java deleted file mode 100644 index 4780a8cc443..00000000000 --- a/access/src/test/java/org/springframework/security/access/intercept/method/MethodInvocationPrivilegeEvaluatorTests.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.intercept.method; - -import java.util.List; - -import org.aopalliance.intercept.MethodInvocation; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.AccessDecisionManager; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.ITargetObject; -import org.springframework.security.access.OtherTargetObject; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.access.TargetObject; -import org.springframework.security.access.intercept.MethodInvocationPrivilegeEvaluator; -import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor; -import org.springframework.security.access.method.MethodSecurityMetadataSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.util.MethodInvocationUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willThrow; -import static org.mockito.Mockito.mock; - -/** - * Tests - * {@link org.springframework.security.access.intercept.MethodInvocationPrivilegeEvaluator} - * . - * - * @author Ben Alex - */ -public class MethodInvocationPrivilegeEvaluatorTests { - - private TestingAuthenticationToken token; - - private MethodSecurityInterceptor interceptor; - - private AccessDecisionManager adm; - - private MethodSecurityMetadataSource mds; - - private final List role = SecurityConfig.createList("ROLE_IGNORED"); - - @BeforeEach - public final void setUp() { - SecurityContextHolder.clearContext(); - this.interceptor = new MethodSecurityInterceptor(); - this.token = new TestingAuthenticationToken("Test", "Password", "ROLE_SOMETHING"); - this.adm = mock(AccessDecisionManager.class); - AuthenticationManager authman = mock(AuthenticationManager.class); - this.mds = mock(MethodSecurityMetadataSource.class); - this.interceptor.setAccessDecisionManager(this.adm); - this.interceptor.setAuthenticationManager(authman); - this.interceptor.setSecurityMetadataSource(this.mds); - } - - @Test - public void allowsAccessUsingCreate() throws Exception { - Object object = new TargetObject(); - final MethodInvocation mi = MethodInvocationUtils.create(object, "makeLowerCase", "foobar"); - MethodInvocationPrivilegeEvaluator mipe = new MethodInvocationPrivilegeEvaluator(); - given(this.mds.getAttributes(mi)).willReturn(this.role); - mipe.setSecurityInterceptor(this.interceptor); - mipe.afterPropertiesSet(); - assertThat(mipe.isAllowed(mi, this.token)).isTrue(); - } - - @Test - public void allowsAccessUsingCreateFromClass() { - final MethodInvocation mi = MethodInvocationUtils.createFromClass(new OtherTargetObject(), ITargetObject.class, - "makeLowerCase", new Class[] { String.class }, new Object[] { "Hello world" }); - MethodInvocationPrivilegeEvaluator mipe = new MethodInvocationPrivilegeEvaluator(); - mipe.setSecurityInterceptor(this.interceptor); - given(this.mds.getAttributes(mi)).willReturn(this.role); - assertThat(mipe.isAllowed(mi, this.token)).isTrue(); - } - - @Test - public void declinesAccessUsingCreate() { - Object object = new TargetObject(); - final MethodInvocation mi = MethodInvocationUtils.create(object, "makeLowerCase", "foobar"); - MethodInvocationPrivilegeEvaluator mipe = new MethodInvocationPrivilegeEvaluator(); - mipe.setSecurityInterceptor(this.interceptor); - given(this.mds.getAttributes(mi)).willReturn(this.role); - willThrow(new AccessDeniedException("rejected")).given(this.adm).decide(this.token, mi, this.role); - assertThat(mipe.isAllowed(mi, this.token)).isFalse(); - } - - @Test - public void declinesAccessUsingCreateFromClass() { - final MethodInvocation mi = MethodInvocationUtils.createFromClass(new OtherTargetObject(), ITargetObject.class, - "makeLowerCase", new Class[] { String.class }, new Object[] { "helloWorld" }); - MethodInvocationPrivilegeEvaluator mipe = new MethodInvocationPrivilegeEvaluator(); - mipe.setSecurityInterceptor(this.interceptor); - given(this.mds.getAttributes(mi)).willReturn(this.role); - willThrow(new AccessDeniedException("rejected")).given(this.adm).decide(this.token, mi, this.role); - assertThat(mipe.isAllowed(mi, this.token)).isFalse(); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/intercept/method/MockMethodInvocation.java b/access/src/test/java/org/springframework/security/access/intercept/method/MockMethodInvocation.java deleted file mode 100644 index c11061983dd..00000000000 --- a/access/src/test/java/org/springframework/security/access/intercept/method/MockMethodInvocation.java +++ /dev/null @@ -1,75 +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.access.intercept.method; - -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Method; - -import org.aopalliance.intercept.MethodInvocation; - -@SuppressWarnings("unchecked") -public class MockMethodInvocation implements MethodInvocation { - - private Method method; - - private Object targetObject; - - private Object[] arguments = new Object[0]; - - public MockMethodInvocation(Object targetObject, Class clazz, String methodName, Class[] parameterTypes, - Object[] arguments) throws NoSuchMethodException { - this(targetObject, clazz, methodName, parameterTypes); - this.arguments = arguments; - } - - public MockMethodInvocation(Object targetObject, Class clazz, String methodName, Class... parameterTypes) - throws NoSuchMethodException { - this(targetObject, clazz.getMethod(methodName, parameterTypes)); - this.targetObject = targetObject; - } - - public MockMethodInvocation(Object targetObject, Method method) { - this.targetObject = targetObject; - this.method = method; - } - - @Override - public Object[] getArguments() { - return this.arguments; - } - - @Override - public Method getMethod() { - return this.method; - } - - @Override - public AccessibleObject getStaticPart() { - return null; - } - - @Override - public Object getThis() { - return this.targetObject; - } - - @Override - public Object proceed() { - return null; - } - -} diff --git a/access/src/test/java/org/springframework/security/access/method/DelegatingMethodSecurityMetadataSourceTests.java b/access/src/test/java/org/springframework/security/access/method/DelegatingMethodSecurityMetadataSourceTests.java deleted file mode 100644 index 82451764d0f..00000000000 --- a/access/src/test/java/org/springframework/security/access/method/DelegatingMethodSecurityMetadataSourceTests.java +++ /dev/null @@ -1,80 +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.access.method; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.aopalliance.intercept.MethodInvocation; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentMatchers; - -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.util.SimpleMethodInvocation; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * @author Luke Taylor - */ -@SuppressWarnings({ "unchecked" }) -public class DelegatingMethodSecurityMetadataSourceTests { - - DelegatingMethodSecurityMetadataSource mds; - - @Test - public void returnsEmptyListIfDelegateReturnsNull() throws Exception { - List sources = new ArrayList(); - MethodSecurityMetadataSource delegate = mock(MethodSecurityMetadataSource.class); - given(delegate.getAttributes(ArgumentMatchers.any(), ArgumentMatchers.any(Class.class))) - .willReturn(null); - sources.add(delegate); - this.mds = new DelegatingMethodSecurityMetadataSource(sources); - assertThat(this.mds.getMethodSecurityMetadataSources()).isSameAs(sources); - assertThat(this.mds.getAllConfigAttributes()).isEmpty(); - MethodInvocation mi = new SimpleMethodInvocation(null, String.class.getMethod("toString")); - assertThat(this.mds.getAttributes(mi)).isEqualTo(Collections.emptyList()); - // Exercise the cached case - assertThat(this.mds.getAttributes(mi)).isEqualTo(Collections.emptyList()); - } - - @Test - public void returnsDelegateAttributes() throws Exception { - List sources = new ArrayList(); - MethodSecurityMetadataSource delegate = mock(MethodSecurityMetadataSource.class); - ConfigAttribute ca = mock(ConfigAttribute.class); - List attributes = Arrays.asList(ca); - Method toString = String.class.getMethod("toString"); - given(delegate.getAttributes(toString, String.class)).willReturn(attributes); - sources.add(delegate); - this.mds = new DelegatingMethodSecurityMetadataSource(sources); - assertThat(this.mds.getMethodSecurityMetadataSources()).isSameAs(sources); - assertThat(this.mds.getAllConfigAttributes()).isEmpty(); - MethodInvocation mi = new SimpleMethodInvocation("", toString); - assertThat(this.mds.getAttributes(mi)).isSameAs(attributes); - // Exercise the cached case - assertThat(this.mds.getAttributes(mi)).isSameAs(attributes); - assertThat(this.mds.getAttributes(new SimpleMethodInvocation(null, String.class.getMethod("length")))) - .isEmpty(); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/prepost/PostInvocationAdviceProviderTests.java b/access/src/test/java/org/springframework/security/access/prepost/PostInvocationAdviceProviderTests.java deleted file mode 100644 index c7d13b84af1..00000000000 --- a/access/src/test/java/org/springframework/security/access/prepost/PostInvocationAdviceProviderTests.java +++ /dev/null @@ -1,59 +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.access.prepost; - -import org.aopalliance.intercept.MethodInvocation; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.aop.ProxyMethodInvocation; -import org.springframework.security.access.intercept.aspectj.MethodInvocationAdapter; - -import static org.assertj.core.api.Assertions.assertThat; - -@ExtendWith(MockitoExtension.class) -public class PostInvocationAdviceProviderTests { - - @Mock - private PostInvocationAuthorizationAdvice authorizationAdvice; - - private PostInvocationAdviceProvider postInvocationAdviceProvider; - - @BeforeEach - public void setUp() { - this.postInvocationAdviceProvider = new PostInvocationAdviceProvider(this.authorizationAdvice); - } - - @Test - public void supportsMethodInvocation() { - assertThat(this.postInvocationAdviceProvider.supports(MethodInvocation.class)).isTrue(); - } - - @Test - public void supportsProxyMethodInvocation() { - assertThat(this.postInvocationAdviceProvider.supports(ProxyMethodInvocation.class)).isTrue(); - } - - @Test - public void supportsMethodInvocationAdapter() { - assertThat(this.postInvocationAdviceProvider.supports(MethodInvocationAdapter.class)).isTrue(); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/prepost/PreInvocationAuthorizationAdviceVoterTests.java b/access/src/test/java/org/springframework/security/access/prepost/PreInvocationAuthorizationAdviceVoterTests.java deleted file mode 100644 index 26e34dd9387..00000000000 --- a/access/src/test/java/org/springframework/security/access/prepost/PreInvocationAuthorizationAdviceVoterTests.java +++ /dev/null @@ -1,60 +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.access.prepost; - -import org.aopalliance.intercept.MethodInvocation; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.aop.ProxyMethodInvocation; -import org.springframework.security.access.intercept.aspectj.MethodInvocationAdapter; - -import static org.assertj.core.api.Assertions.assertThat; - -@ExtendWith(MockitoExtension.class) -public class PreInvocationAuthorizationAdviceVoterTests { - - @Mock - private PreInvocationAuthorizationAdvice authorizationAdvice; - - private PreInvocationAuthorizationAdviceVoter voter; - - @BeforeEach - public void setUp() { - this.voter = new PreInvocationAuthorizationAdviceVoter(this.authorizationAdvice); - } - - @Test - public void supportsMethodInvocation() { - assertThat(this.voter.supports(MethodInvocation.class)).isTrue(); - } - - // SEC-2031 - @Test - public void supportsProxyMethodInvocation() { - assertThat(this.voter.supports(ProxyMethodInvocation.class)).isTrue(); - } - - @Test - public void supportsMethodInvocationAdapter() { - assertThat(this.voter.supports(MethodInvocationAdapter.class)).isTrue(); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/vote/AbstractAccessDecisionManagerTests.java b/access/src/test/java/org/springframework/security/access/vote/AbstractAccessDecisionManagerTests.java deleted file mode 100644 index 28583840152..00000000000 --- a/access/src/test/java/org/springframework/security/access/vote/AbstractAccessDecisionManagerTests.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.vote; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Vector; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.core.Authentication; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests {@link AbstractAccessDecisionManager}. - * - * @author Ben Alex - */ -@SuppressWarnings("unchecked") -public class AbstractAccessDecisionManagerTests { - - @Test - public void testAllowIfAccessDecisionManagerDefaults() { - List list = new Vector(); - DenyAgainVoter denyVoter = new DenyAgainVoter(); - list.add(denyVoter); - MockDecisionManagerImpl mock = new MockDecisionManagerImpl(list); - assertThat(!mock.isAllowIfAllAbstainDecisions()).isTrue(); // default - mock.setAllowIfAllAbstainDecisions(true); - assertThat(mock.isAllowIfAllAbstainDecisions()).isTrue(); // changed - } - - @Test - public void testDelegatesSupportsClassRequests() { - List list = new Vector(); - list.add(new DenyVoter()); - list.add(new MockStringOnlyVoter()); - MockDecisionManagerImpl mock = new MockDecisionManagerImpl(list); - assertThat(mock.supports(String.class)).isTrue(); - assertThat(!mock.supports(Integer.class)).isTrue(); - } - - @Test - public void testDelegatesSupportsRequests() { - List list = new Vector(); - DenyVoter voter = new DenyVoter(); - DenyAgainVoter denyVoter = new DenyAgainVoter(); - list.add(voter); - list.add(denyVoter); - MockDecisionManagerImpl mock = new MockDecisionManagerImpl(list); - ConfigAttribute attr = new SecurityConfig("DENY_AGAIN_FOR_SURE"); - assertThat(mock.supports(attr)).isTrue(); - ConfigAttribute badAttr = new SecurityConfig("WE_DONT_SUPPORT_THIS"); - assertThat(!mock.supports(badAttr)).isTrue(); - } - - @Test - public void testProperlyStoresListOfVoters() { - List list = new Vector(); - DenyVoter voter = new DenyVoter(); - DenyAgainVoter denyVoter = new DenyAgainVoter(); - list.add(voter); - list.add(denyVoter); - MockDecisionManagerImpl mock = new MockDecisionManagerImpl(list); - assertThat(mock.getDecisionVoters()).hasSize(list.size()); - } - - @Test - public void testRejectsEmptyList() { - assertThatIllegalArgumentException().isThrownBy(() -> new MockDecisionManagerImpl(Collections.emptyList())); - } - - @Test - public void testRejectsNullVotersList() { - assertThatIllegalArgumentException().isThrownBy(() -> new MockDecisionManagerImpl(null)); - } - - @Test - public void testRoleVoterAlwaysReturnsTrueToSupports() { - RoleVoter rv = new RoleVoter(); - assertThat(rv.supports(String.class)).isTrue(); - } - - @Test - public void testWillNotStartIfDecisionVotersNotSet() { - assertThatIllegalArgumentException().isThrownBy(() -> new MockDecisionManagerImpl(null)); - } - - private class MockDecisionManagerImpl extends AbstractAccessDecisionManager { - - protected MockDecisionManagerImpl(List> decisionVoters) { - super(decisionVoters); - } - - @Override - public void decide(Authentication authentication, Object object, Collection configAttributes) { - } - - } - - private class MockStringOnlyVoter implements AccessDecisionVoter { - - @Override - public boolean supports(Class clazz) { - return String.class.isAssignableFrom(clazz); - } - - @Override - public boolean supports(ConfigAttribute attribute) { - throw new UnsupportedOperationException("mock method not implemented"); - } - - @Override - public int vote(Authentication authentication, Object object, Collection attributes) { - throw new UnsupportedOperationException("mock method not implemented"); - } - - } - -} diff --git a/access/src/test/java/org/springframework/security/access/vote/AbstractAclVoterTests.java b/access/src/test/java/org/springframework/security/access/vote/AbstractAclVoterTests.java deleted file mode 100644 index a4d5ab09a19..00000000000 --- a/access/src/test/java/org/springframework/security/access/vote/AbstractAclVoterTests.java +++ /dev/null @@ -1,84 +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.access.vote; - -import java.util.ArrayList; -import java.util.Collection; - -import org.aopalliance.intercept.MethodInvocation; -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.core.Authentication; -import org.springframework.security.util.MethodInvocationUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Luke Taylor - */ -public class AbstractAclVoterTests { - - private AbstractAclVoter voter = new AbstractAclVoter() { - @Override - public boolean supports(ConfigAttribute attribute) { - return false; - } - - @Override - public int vote(Authentication authentication, MethodInvocation object, - Collection attributes) { - return 0; - } - }; - - @Test - public void supportsMethodInvocations() { - assertThat(this.voter.supports(MethodInvocation.class)).isTrue(); - assertThat(this.voter.supports(String.class)).isFalse(); - } - - @Test - public void expectedDomainObjectArgumentIsReturnedFromMethodInvocation() { - this.voter.setProcessDomainObjectClass(String.class); - MethodInvocation mi = MethodInvocationUtils.create(new TestClass(), "methodTakingAString", "The Argument"); - assertThat(this.voter.getDomainObjectInstance(mi)).isEqualTo("The Argument"); - } - - @Test - public void correctArgumentIsSelectedFromMultipleArgs() { - this.voter.setProcessDomainObjectClass(String.class); - MethodInvocation mi = MethodInvocationUtils.create(new TestClass(), "methodTakingAListAndAString", - new ArrayList<>(), "The Argument"); - assertThat(this.voter.getDomainObjectInstance(mi)).isEqualTo("The Argument"); - } - - @SuppressWarnings("unused") - private static class TestClass { - - public void methodTakingAString(String arg) { - } - - public void methodTaking2Strings(String arg1, String arg2) { - } - - public void methodTakingAListAndAString(ArrayList arg1, String arg2) { - } - - } - -} diff --git a/access/src/test/java/org/springframework/security/access/vote/AffirmativeBasedTests.java b/access/src/test/java/org/springframework/security/access/vote/AffirmativeBasedTests.java deleted file mode 100644 index 74a7c779d1e..00000000000 --- a/access/src/test/java/org/springframework/security/access/vote/AffirmativeBasedTests.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.vote; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests {@link AffirmativeBased}. - * - * @author Ben Alex - */ -public class AffirmativeBasedTests { - - private final List attrs = new ArrayList<>(); - - private final Authentication user = new TestingAuthenticationToken("somebody", "password", "ROLE_1", "ROLE_2"); - - private AffirmativeBased mgr; - - private AccessDecisionVoter grant; - - private AccessDecisionVoter abstain; - - private AccessDecisionVoter deny; - - @BeforeEach - @SuppressWarnings("unchecked") - public void setup() { - this.grant = mock(AccessDecisionVoter.class); - this.abstain = mock(AccessDecisionVoter.class); - this.deny = mock(AccessDecisionVoter.class); - given(this.grant.vote(any(Authentication.class), any(Object.class), any(List.class))) - .willReturn(AccessDecisionVoter.ACCESS_GRANTED); - given(this.abstain.vote(any(Authentication.class), any(Object.class), any(List.class))) - .willReturn(AccessDecisionVoter.ACCESS_ABSTAIN); - given(this.deny.vote(any(Authentication.class), any(Object.class), any(List.class))) - .willReturn(AccessDecisionVoter.ACCESS_DENIED); - } - - @Test - public void oneAffirmativeVoteOneDenyVoteOneAbstainVoteGrantsAccess() throws Exception { - this.mgr = new AffirmativeBased( - Arrays.>asList(this.grant, this.deny, this.abstain)); - this.mgr.afterPropertiesSet(); - this.mgr.decide(this.user, new Object(), this.attrs); - } - - @Test - public void oneDenyVoteOneAbstainVoteOneAffirmativeVoteGrantsAccess() { - this.mgr = new AffirmativeBased( - Arrays.>asList(this.deny, this.abstain, this.grant)); - this.mgr.decide(this.user, new Object(), this.attrs); - } - - @Test - public void oneAffirmativeVoteTwoAbstainVotesGrantsAccess() { - this.mgr = new AffirmativeBased( - Arrays.>asList(this.grant, this.abstain, this.abstain)); - this.mgr.decide(this.user, new Object(), this.attrs); - } - - @Test - public void oneDenyVoteTwoAbstainVotesDeniesAccess() { - this.mgr = new AffirmativeBased( - Arrays.>asList(this.deny, this.abstain, this.abstain)); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.mgr.decide(this.user, new Object(), this.attrs)); - } - - @Test - public void onlyAbstainVotesDeniesAccessWithDefault() { - this.mgr = new AffirmativeBased( - Arrays.>asList(this.abstain, this.abstain, this.abstain)); - assertThat(!this.mgr.isAllowIfAllAbstainDecisions()).isTrue(); // check default - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.mgr.decide(this.user, new Object(), this.attrs)); - } - - @Test - public void testThreeAbstainVotesGrantsAccessIfAllowIfAllAbstainDecisionsIsSet() { - this.mgr = new AffirmativeBased( - Arrays.>asList(this.abstain, this.abstain, this.abstain)); - this.mgr.setAllowIfAllAbstainDecisions(true); - assertThat(this.mgr.isAllowIfAllAbstainDecisions()).isTrue(); // check changed - this.mgr.decide(this.user, new Object(), this.attrs); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/vote/AuthenticatedVoterTests.java b/access/src/test/java/org/springframework/security/access/vote/AuthenticatedVoterTests.java deleted file mode 100644 index 9c153dbf6fb..00000000000 --- a/access/src/test/java/org/springframework/security/access/vote/AuthenticatedVoterTests.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.vote; - -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.authentication.RememberMeAuthenticationToken; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests {@link AuthenticatedVoter}. - * - * @author Ben Alex - */ -public class AuthenticatedVoterTests { - - private Authentication createAnonymous() { - return new AnonymousAuthenticationToken("ignored", "ignored", AuthorityUtils.createAuthorityList("ignored")); - } - - private Authentication createFullyAuthenticated() { - return UsernamePasswordAuthenticationToken.authenticated("ignored", "ignored", - AuthorityUtils.createAuthorityList("ignored")); - } - - private Authentication createRememberMe() { - return new RememberMeAuthenticationToken("ignored", "ignored", AuthorityUtils.createAuthorityList("ignored")); - } - - @Test - public void testAnonymousWorks() { - AuthenticatedVoter voter = new AuthenticatedVoter(); - List def = SecurityConfig.createList(AuthenticatedVoter.IS_AUTHENTICATED_ANONYMOUSLY); - assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createAnonymous(), null, def)); - assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createRememberMe(), null, def)); - assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createFullyAuthenticated(), null, def)); - assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(null, null, def)); - } - - @Test - public void testFullyWorks() { - AuthenticatedVoter voter = new AuthenticatedVoter(); - List def = SecurityConfig.createList(AuthenticatedVoter.IS_AUTHENTICATED_FULLY); - assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(createAnonymous(), null, def)); - assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(createRememberMe(), null, def)); - assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createFullyAuthenticated(), null, def)); - assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(null, null, def)); - } - - @Test - public void testRememberMeWorks() { - AuthenticatedVoter voter = new AuthenticatedVoter(); - List def = SecurityConfig.createList(AuthenticatedVoter.IS_AUTHENTICATED_REMEMBERED); - assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(createAnonymous(), null, def)); - assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createRememberMe(), null, def)); - assertThat(AccessDecisionVoter.ACCESS_GRANTED).isEqualTo(voter.vote(createFullyAuthenticated(), null, def)); - assertThat(AccessDecisionVoter.ACCESS_DENIED).isEqualTo(voter.vote(null, null, def)); - } - - @Test - public void testSetterRejectsNull() { - AuthenticatedVoter voter = new AuthenticatedVoter(); - assertThatIllegalArgumentException().isThrownBy(() -> voter.setAuthenticationTrustResolver(null)); - } - - @Test - public void testSupports() { - AuthenticatedVoter voter = new AuthenticatedVoter(); - assertThat(voter.supports(String.class)).isTrue(); - assertThat(voter.supports(new SecurityConfig(AuthenticatedVoter.IS_AUTHENTICATED_ANONYMOUSLY))).isTrue(); - assertThat(voter.supports(new SecurityConfig(AuthenticatedVoter.IS_AUTHENTICATED_FULLY))).isTrue(); - assertThat(voter.supports(new SecurityConfig(AuthenticatedVoter.IS_AUTHENTICATED_REMEMBERED))).isTrue(); - assertThat(voter.supports(new SecurityConfig("FOO"))).isFalse(); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/vote/ConsensusBasedTests.java b/access/src/test/java/org/springframework/security/access/vote/ConsensusBasedTests.java deleted file mode 100644 index 18cd2f834e6..00000000000 --- a/access/src/test/java/org/springframework/security/access/vote/ConsensusBasedTests.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2004 Acegi Technology Pty Limited - * - * 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.access.vote; - -import java.util.List; -import java.util.Vector; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.authentication.TestingAuthenticationToken; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests {@link ConsensusBased}. - * - * @author Ben Alex - */ -public class ConsensusBasedTests { - - @Test - public void testOneAffirmativeVoteOneDenyVoteOneAbstainVoteDeniesAccessWithoutDefault() { - TestingAuthenticationToken auth = makeTestToken(); - ConsensusBased mgr = makeDecisionManager(); - mgr.setAllowIfEqualGrantedDeniedDecisions(false); - assertThat(!mgr.isAllowIfEqualGrantedDeniedDecisions()).isTrue(); // check changed - List config = SecurityConfig.createList("ROLE_1", "DENY_FOR_SURE"); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> mgr.decide(auth, new Object(), config)); - } - - @Test - public void testOneAffirmativeVoteOneDenyVoteOneAbstainVoteGrantsAccessWithDefault() { - TestingAuthenticationToken auth = makeTestToken(); - ConsensusBased mgr = makeDecisionManager(); - assertThat(mgr.isAllowIfEqualGrantedDeniedDecisions()).isTrue(); // check default - List config = SecurityConfig.createList("ROLE_1", "DENY_FOR_SURE"); - mgr.decide(auth, new Object(), config); - } - - @Test - public void testOneAffirmativeVoteTwoAbstainVotesGrantsAccess() { - TestingAuthenticationToken auth = makeTestToken(); - ConsensusBased mgr = makeDecisionManager(); - mgr.decide(auth, new Object(), SecurityConfig.createList("ROLE_2")); - } - - @Test - public void testOneDenyVoteTwoAbstainVotesDeniesAccess() { - TestingAuthenticationToken auth = makeTestToken(); - ConsensusBased mgr = makeDecisionManager(); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> mgr.decide(auth, new Object(), SecurityConfig.createList("ROLE_WE_DO_NOT_HAVE"))); - } - - @Test - public void testThreeAbstainVotesDeniesAccessWithDefault() { - TestingAuthenticationToken auth = makeTestToken(); - ConsensusBased mgr = makeDecisionManager(); - assertThat(!mgr.isAllowIfAllAbstainDecisions()).isTrue(); // check default - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> mgr.decide(auth, new Object(), SecurityConfig.createList("IGNORED_BY_ALL"))); - } - - @Test - public void testThreeAbstainVotesGrantsAccessWithoutDefault() { - TestingAuthenticationToken auth = makeTestToken(); - ConsensusBased mgr = makeDecisionManager(); - mgr.setAllowIfAllAbstainDecisions(true); - assertThat(mgr.isAllowIfAllAbstainDecisions()).isTrue(); // check changed - mgr.decide(auth, new Object(), SecurityConfig.createList("IGNORED_BY_ALL")); - } - - @Test - public void testTwoAffirmativeVotesTwoAbstainVotesGrantsAccess() { - TestingAuthenticationToken auth = makeTestToken(); - ConsensusBased mgr = makeDecisionManager(); - mgr.decide(auth, new Object(), SecurityConfig.createList("ROLE_1", "ROLE_2")); - } - - private ConsensusBased makeDecisionManager() { - RoleVoter roleVoter = new RoleVoter(); - DenyVoter denyForSureVoter = new DenyVoter(); - DenyAgainVoter denyAgainForSureVoter = new DenyAgainVoter(); - List> voters = new Vector<>(); - voters.add(roleVoter); - voters.add(denyForSureVoter); - voters.add(denyAgainForSureVoter); - return new ConsensusBased(voters); - } - - private TestingAuthenticationToken makeTestToken() { - return new TestingAuthenticationToken("somebody", "password", "ROLE_1", "ROLE_2"); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/vote/DenyAgainVoter.java b/access/src/test/java/org/springframework/security/access/vote/DenyAgainVoter.java deleted file mode 100644 index 9fcfc7fb3a1..00000000000 --- a/access/src/test/java/org/springframework/security/access/vote/DenyAgainVoter.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.vote; - -import java.util.Collection; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.core.Authentication; - -/** - * Implementation of an {@link AccessDecisionVoter} for unit testing. - *

- * If the {@link ConfigAttribute#getAttribute()} has a value of - * DENY_AGAIN_FOR_SURE, the voter will vote to deny access. - *

- * All comparisons are case sensitive. - * - * @author Ben Alex - */ -public class DenyAgainVoter implements AccessDecisionVoter { - - @Override - public boolean supports(ConfigAttribute attribute) { - return "DENY_AGAIN_FOR_SURE".equals(attribute.getAttribute()); - } - - @Override - public boolean supports(Class clazz) { - return true; - } - - @Override - public int vote(Authentication authentication, Object object, Collection attributes) { - for (ConfigAttribute attribute : attributes) { - if (this.supports(attribute)) { - return ACCESS_DENIED; - } - } - return ACCESS_ABSTAIN; - } - -} diff --git a/access/src/test/java/org/springframework/security/access/vote/DenyVoter.java b/access/src/test/java/org/springframework/security/access/vote/DenyVoter.java deleted file mode 100644 index 6408c9a5704..00000000000 --- a/access/src/test/java/org/springframework/security/access/vote/DenyVoter.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.vote; - -import java.util.Collection; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.core.Authentication; - -/** - * Implementation of an {@link AccessDecisionVoter} for unit testing. - *

- * If the {@link ConfigAttribute#getAttribute()} has a value of DENY_FOR_SURE - * , the voter will vote to deny access. - *

- *

- * All comparisons are case sensitive. - *

- * - * @author Ben Alex - */ -public class DenyVoter implements AccessDecisionVoter { - - @Override - public boolean supports(ConfigAttribute attribute) { - return "DENY_FOR_SURE".equals(attribute.getAttribute()); - } - - @Override - public boolean supports(Class clazz) { - return true; - } - - @Override - public int vote(Authentication authentication, Object object, Collection attributes) { - for (ConfigAttribute attribute : attributes) { - if (this.supports(attribute)) { - return ACCESS_DENIED; - } - } - return ACCESS_ABSTAIN; - } - -} diff --git a/access/src/test/java/org/springframework/security/access/vote/RoleHierarchyVoterTests.java b/access/src/test/java/org/springframework/security/access/vote/RoleHierarchyVoterTests.java deleted file mode 100644 index 4e2fd6f9742..00000000000 --- a/access/src/test/java/org/springframework/security/access/vote/RoleHierarchyVoterTests.java +++ /dev/null @@ -1,40 +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.access.vote; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; -import org.springframework.security.authentication.TestingAuthenticationToken; - -import static org.assertj.core.api.Assertions.assertThat; - -public class RoleHierarchyVoterTests { - - @Test - public void hierarchicalRoleIsIncludedInDecision() { - RoleHierarchyImpl roleHierarchyImpl = RoleHierarchyImpl.fromHierarchy("ROLE_A > ROLE_B"); - // User has role A, role B is required - TestingAuthenticationToken auth = new TestingAuthenticationToken("user", "password", "ROLE_A"); - RoleHierarchyVoter voter = new RoleHierarchyVoter(roleHierarchyImpl); - assertThat(voter.vote(auth, new Object(), SecurityConfig.createList("ROLE_B"))) - .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/vote/RoleVoterTests.java b/access/src/test/java/org/springframework/security/access/vote/RoleVoterTests.java deleted file mode 100644 index f67b91a0c6c..00000000000 --- a/access/src/test/java/org/springframework/security/access/vote/RoleVoterTests.java +++ /dev/null @@ -1,53 +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.access.vote; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Luke Taylor - */ -public class RoleVoterTests { - - @Test - public void oneMatchingAttributeGrantsAccess() { - RoleVoter voter = new RoleVoter(); - voter.setRolePrefix(""); - Authentication userAB = new TestingAuthenticationToken("user", "pass", "A", "B"); - // Vote on attribute list that has two attributes A and C (i.e. only one matching) - assertThat(voter.vote(userAB, this, SecurityConfig.createList("A", "C"))) - .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); - } - - // SEC-3128 - @Test - public void nullAuthenticationDenies() { - RoleVoter voter = new RoleVoter(); - voter.setRolePrefix(""); - Authentication notAuthenitcated = null; - assertThat(voter.vote(notAuthenitcated, this, SecurityConfig.createList("A"))) - .isEqualTo(AccessDecisionVoter.ACCESS_DENIED); - } - -} diff --git a/access/src/test/java/org/springframework/security/access/vote/UnanimousBasedTests.java b/access/src/test/java/org/springframework/security/access/vote/UnanimousBasedTests.java deleted file mode 100644 index 8674cf11a91..00000000000 --- a/access/src/test/java/org/springframework/security/access/vote/UnanimousBasedTests.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.access.vote; - -import java.util.List; -import java.util.Vector; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.authentication.TestingAuthenticationToken; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests {@link UnanimousBased}. - * - * @author Ben Alex - */ -public class UnanimousBasedTests { - - private UnanimousBased makeDecisionManager() { - RoleVoter roleVoter = new RoleVoter(); - DenyVoter denyForSureVoter = new DenyVoter(); - DenyAgainVoter denyAgainForSureVoter = new DenyAgainVoter(); - List> voters = new Vector<>(); - voters.add(roleVoter); - voters.add(denyForSureVoter); - voters.add(denyAgainForSureVoter); - return new UnanimousBased(voters); - } - - private UnanimousBased makeDecisionManagerWithFooBarPrefix() { - RoleVoter roleVoter = new RoleVoter(); - roleVoter.setRolePrefix("FOOBAR_"); - DenyVoter denyForSureVoter = new DenyVoter(); - DenyAgainVoter denyAgainForSureVoter = new DenyAgainVoter(); - List> voters = new Vector<>(); - voters.add(roleVoter); - voters.add(denyForSureVoter); - voters.add(denyAgainForSureVoter); - return new UnanimousBased(voters); - } - - private TestingAuthenticationToken makeTestToken() { - return new TestingAuthenticationToken("somebody", "password", "ROLE_1", "ROLE_2"); - } - - private TestingAuthenticationToken makeTestTokenWithFooBarPrefix() { - return new TestingAuthenticationToken("somebody", "password", "FOOBAR_1", "FOOBAR_2"); - } - - @Test - public void testOneAffirmativeVoteOneDenyVoteOneAbstainVoteDeniesAccess() { - TestingAuthenticationToken auth = makeTestToken(); - UnanimousBased mgr = makeDecisionManager(); - List config = SecurityConfig.createList(new String[] { "ROLE_1", "DENY_FOR_SURE" }); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> mgr.decide(auth, new Object(), config)); - } - - @Test - public void testOneAffirmativeVoteTwoAbstainVotesGrantsAccess() { - TestingAuthenticationToken auth = makeTestToken(); - UnanimousBased mgr = makeDecisionManager(); - List config = SecurityConfig.createList("ROLE_2"); - mgr.decide(auth, new Object(), config); - } - - @Test - public void testOneDenyVoteTwoAbstainVotesDeniesAccess() { - TestingAuthenticationToken auth = makeTestToken(); - UnanimousBased mgr = makeDecisionManager(); - List config = SecurityConfig.createList("ROLE_WE_DO_NOT_HAVE"); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> mgr.decide(auth, new Object(), config)); - } - - @Test - public void testRoleVoterPrefixObserved() { - TestingAuthenticationToken auth = makeTestTokenWithFooBarPrefix(); - UnanimousBased mgr = makeDecisionManagerWithFooBarPrefix(); - List config = SecurityConfig.createList(new String[] { "FOOBAR_1", "FOOBAR_2" }); - mgr.decide(auth, new Object(), config); - } - - @Test - public void testThreeAbstainVotesDeniesAccessWithDefault() { - TestingAuthenticationToken auth = makeTestToken(); - UnanimousBased mgr = makeDecisionManager(); - assertThat(!mgr.isAllowIfAllAbstainDecisions()).isTrue(); // check default - List config = SecurityConfig.createList("IGNORED_BY_ALL"); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> mgr.decide(auth, new Object(), config)); - } - - @Test - public void testThreeAbstainVotesGrantsAccessWithoutDefault() { - TestingAuthenticationToken auth = makeTestToken(); - UnanimousBased mgr = makeDecisionManager(); - mgr.setAllowIfAllAbstainDecisions(true); - assertThat(mgr.isAllowIfAllAbstainDecisions()).isTrue(); // check changed - List config = SecurityConfig.createList("IGNORED_BY_ALL"); - mgr.decide(auth, new Object(), config); - } - - @Test - public void testTwoAffirmativeVotesTwoAbstainVotesGrantsAccess() { - TestingAuthenticationToken auth = makeTestToken(); - UnanimousBased mgr = makeDecisionManager(); - List config = SecurityConfig.createList(new String[] { "ROLE_1", "ROLE_2" }); - mgr.decide(auth, new Object(), config); - } - -} diff --git a/access/src/test/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationCollectionFilteringProviderTests.java b/access/src/test/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationCollectionFilteringProviderTests.java deleted file mode 100644 index ce44f41694b..00000000000 --- a/access/src/test/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationCollectionFilteringProviderTests.java +++ /dev/null @@ -1,93 +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.afterinvocation; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.acls.model.Acl; -import org.springframework.security.acls.model.AclService; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; -import org.springframework.security.acls.model.Permission; -import org.springframework.security.acls.model.SidRetrievalStrategy; -import org.springframework.security.core.Authentication; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -/** - * @author Luke Taylor - */ -@SuppressWarnings({ "unchecked" }) -public class AclEntryAfterInvocationCollectionFilteringProviderTests { - - @Test - public void objectsAreRemovedIfPermissionDenied() { - AclService service = mock(AclService.class); - Acl acl = mock(Acl.class); - given(acl.isGranted(any(), any(), anyBoolean())).willReturn(false); - given(service.readAclById(any(), any())).willReturn(acl); - AclEntryAfterInvocationCollectionFilteringProvider provider = new AclEntryAfterInvocationCollectionFilteringProvider( - service, Arrays.asList(mock(Permission.class))); - provider.setObjectIdentityRetrievalStrategy(mock(ObjectIdentityRetrievalStrategy.class)); - provider.setProcessDomainObjectClass(Object.class); - provider.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class)); - Object returned = provider.decide(mock(Authentication.class), new Object(), - SecurityConfig.createList("AFTER_ACL_COLLECTION_READ"), - new ArrayList(Arrays.asList(new Object(), new Object()))); - assertThat(returned).isInstanceOf(List.class); - assertThat(((List) returned)).isEmpty(); - returned = provider.decide(mock(Authentication.class), new Object(), - SecurityConfig.createList("UNSUPPORTED", "AFTER_ACL_COLLECTION_READ"), - new Object[] { new Object(), new Object() }); - assertThat(returned instanceof Object[]).isTrue(); - assertThat(((Object[]) returned).length == 0).isTrue(); - } - - @Test - public void accessIsGrantedIfNoAttributesDefined() { - AclEntryAfterInvocationCollectionFilteringProvider provider = new AclEntryAfterInvocationCollectionFilteringProvider( - mock(AclService.class), Arrays.asList(mock(Permission.class))); - Object returned = new Object(); - assertThat(returned).isSameAs(provider.decide(mock(Authentication.class), new Object(), - Collections.emptyList(), returned)); - } - - @Test - public void nullReturnObjectIsIgnored() { - AclService service = mock(AclService.class); - AclEntryAfterInvocationCollectionFilteringProvider provider = new AclEntryAfterInvocationCollectionFilteringProvider( - service, Arrays.asList(mock(Permission.class))); - assertThat(provider.decide(mock(Authentication.class), new Object(), - SecurityConfig.createList("AFTER_ACL_COLLECTION_READ"), null)) - .isNull(); - verify(service, never()).readAclById(any(ObjectIdentity.class), any(List.class)); - } - -} diff --git a/access/src/test/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationProviderTests.java b/access/src/test/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationProviderTests.java deleted file mode 100644 index 8aec2c102a3..00000000000 --- a/access/src/test/java/org/springframework/security/acls/afterinvocation/AclEntryAfterInvocationProviderTests.java +++ /dev/null @@ -1,134 +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.afterinvocation; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.acls.model.Acl; -import org.springframework.security.acls.model.AclService; -import org.springframework.security.acls.model.NotFoundException; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; -import org.springframework.security.acls.model.Permission; -import org.springframework.security.acls.model.SidRetrievalStrategy; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.SpringSecurityMessageSource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -/** - * @author Luke Taylor - */ -@SuppressWarnings({ "unchecked" }) -public class AclEntryAfterInvocationProviderTests { - - @Test - public void rejectsMissingPermissions() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new AclEntryAfterInvocationProvider(mock(AclService.class), null)); - assertThatIllegalArgumentException().isThrownBy( - () -> new AclEntryAfterInvocationProvider(mock(AclService.class), Collections.emptyList())); - } - - @Test - public void accessIsAllowedIfPermissionIsGranted() { - AclService service = mock(AclService.class); - Acl acl = mock(Acl.class); - given(acl.isGranted(any(List.class), any(List.class), anyBoolean())).willReturn(true); - given(service.readAclById(any(), any())).willReturn(acl); - AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(service, - Arrays.asList(mock(Permission.class))); - provider.setMessageSource(new SpringSecurityMessageSource()); - provider.setObjectIdentityRetrievalStrategy(mock(ObjectIdentityRetrievalStrategy.class)); - provider.setProcessDomainObjectClass(Object.class); - provider.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class)); - Object returned = new Object(); - assertThat(returned).isSameAs(provider.decide(mock(Authentication.class), new Object(), - SecurityConfig.createList("AFTER_ACL_READ"), returned)); - } - - @Test - public void accessIsGrantedIfNoAttributesDefined() { - AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(mock(AclService.class), - Arrays.asList(mock(Permission.class))); - Object returned = new Object(); - assertThat(returned).isSameAs(provider.decide(mock(Authentication.class), new Object(), - Collections.emptyList(), returned)); - } - - @Test - public void accessIsGrantedIfObjectTypeNotSupported() { - AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(mock(AclService.class), - Arrays.asList(mock(Permission.class))); - provider.setProcessDomainObjectClass(String.class); - // Not a String - Object returned = new Object(); - assertThat(returned).isSameAs(provider.decide(mock(Authentication.class), new Object(), - SecurityConfig.createList("AFTER_ACL_READ"), returned)); - } - - @Test - public void accessIsDeniedIfPermissionIsNotGranted() { - AclService service = mock(AclService.class); - Acl acl = mock(Acl.class); - given(acl.isGranted(any(List.class), any(List.class), anyBoolean())).willReturn(false); - // Try a second time with no permissions found - given(acl.isGranted(any(), any(List.class), anyBoolean())).willThrow(new NotFoundException("")); - given(service.readAclById(any(), any())).willReturn(acl); - AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(service, - Arrays.asList(mock(Permission.class))); - provider.setProcessConfigAttribute("MY_ATTRIBUTE"); - provider.setMessageSource(new SpringSecurityMessageSource()); - provider.setObjectIdentityRetrievalStrategy(mock(ObjectIdentityRetrievalStrategy.class)); - provider.setProcessDomainObjectClass(Object.class); - provider.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class)); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> provider.decide(mock(Authentication.class), new Object(), - SecurityConfig.createList("UNSUPPORTED", "MY_ATTRIBUTE"), new Object())); - // Second scenario with no acls found - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> provider.decide(mock(Authentication.class), new Object(), - SecurityConfig.createList("UNSUPPORTED", "MY_ATTRIBUTE"), new Object())); - } - - @Test - public void nullReturnObjectIsIgnored() { - AclService service = mock(AclService.class); - AclEntryAfterInvocationProvider provider = new AclEntryAfterInvocationProvider(service, - Arrays.asList(mock(Permission.class))); - assertThat(provider.decide(mock(Authentication.class), new Object(), - SecurityConfig.createList("AFTER_ACL_COLLECTION_READ"), null)) - .isNull(); - verify(service, never()).readAclById(any(ObjectIdentity.class), any(List.class)); - } - -} diff --git a/access/src/test/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactoryTests.java b/access/src/test/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactoryTests.java deleted file mode 100644 index acb8695cf3b..00000000000 --- a/access/src/test/java/org/springframework/security/messaging/access/expression/ExpressionBasedMessageSecurityMetadataSourceFactoryTests.java +++ /dev/null @@ -1,102 +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.messaging.access.expression; - -import java.util.Collection; -import java.util.LinkedHashMap; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.messaging.Message; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.core.Authentication; -import org.springframework.security.messaging.access.intercept.MessageSecurityMetadataSource; -import org.springframework.security.messaging.util.matcher.MessageMatcher; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; - -@ExtendWith(MockitoExtension.class) -public class ExpressionBasedMessageSecurityMetadataSourceFactoryTests { - - @Mock - MessageMatcher matcher1; - - @Mock - MessageMatcher matcher2; - - @Mock - Message message; - - @Mock - Authentication authentication; - - String expression1; - - String expression2; - - LinkedHashMap, String> matcherToExpression; - - MessageSecurityMetadataSource source; - - MessageSecurityExpressionRoot rootObject; - - @BeforeEach - public void setup() { - this.expression1 = "permitAll"; - this.expression2 = "denyAll"; - this.matcherToExpression = new LinkedHashMap<>(); - this.matcherToExpression.put(this.matcher1, this.expression1); - this.matcherToExpression.put(this.matcher2, this.expression2); - this.source = ExpressionBasedMessageSecurityMetadataSourceFactory - .createExpressionMessageMetadataSource(this.matcherToExpression); - this.rootObject = new MessageSecurityExpressionRoot(this.authentication, this.message); - } - - @Test - public void createExpressionMessageMetadataSourceNoMatch() { - Collection attrs = this.source.getAttributes(this.message); - assertThat(attrs).isEmpty(); - } - - @Test - public void createExpressionMessageMetadataSourceMatchFirst() { - given(this.matcher1.matches(this.message)).willReturn(true); - Collection attrs = this.source.getAttributes(this.message); - assertThat(attrs).hasSize(1); - ConfigAttribute attr = attrs.iterator().next(); - assertThat(attr).isInstanceOf(MessageExpressionConfigAttribute.class); - assertThat(((MessageExpressionConfigAttribute) attr).getAuthorizeExpression().getValue(this.rootObject)) - .isEqualTo(true); - } - - @Test - public void createExpressionMessageMetadataSourceMatchSecond() { - given(this.matcher2.matches(this.message)).willReturn(true); - Collection attrs = this.source.getAttributes(this.message); - assertThat(attrs).hasSize(1); - ConfigAttribute attr = attrs.iterator().next(); - assertThat(attr).isInstanceOf(MessageExpressionConfigAttribute.class); - assertThat(((MessageExpressionConfigAttribute) attr).getAuthorizeExpression().getValue(this.rootObject)) - .isEqualTo(false); - } - -} diff --git a/access/src/test/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttributeTests.java b/access/src/test/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttributeTests.java deleted file mode 100644 index cdc083ebdaa..00000000000 --- a/access/src/test/java/org/springframework/security/messaging/access/expression/MessageExpressionConfigAttributeTests.java +++ /dev/null @@ -1,96 +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.messaging.access.expression; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.messaging.Message; -import org.springframework.messaging.simp.SimpMessageHeaderAccessor; -import org.springframework.messaging.support.MessageBuilder; -import org.springframework.security.messaging.util.matcher.MessageMatcher; -import org.springframework.security.messaging.util.matcher.PathPatternMessageMatcher; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -@ExtendWith(MockitoExtension.class) -public class MessageExpressionConfigAttributeTests { - - @Mock - Expression expression; - - @Mock - MessageMatcher matcher; - - MessageExpressionConfigAttribute attribute; - - @BeforeEach - public void setup() { - this.attribute = new MessageExpressionConfigAttribute(this.expression, this.matcher); - } - - @Test - public void constructorNullExpression() { - assertThatIllegalArgumentException().isThrownBy(() -> new MessageExpressionConfigAttribute(null, this.matcher)); - } - - @Test - public void constructorNullMatcher() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new MessageExpressionConfigAttribute(this.expression, null)); - } - - @Test - public void getAuthorizeExpression() { - assertThat(this.attribute.getAuthorizeExpression()).isSameAs(this.expression); - } - - @Test - public void getAttribute() { - assertThat(this.attribute.getAttribute()).isNull(); - } - - @Test - public void toStringUsesExpressionString() { - given(this.expression.getExpressionString()).willReturn("toString"); - assertThat(this.attribute.toString()).isEqualTo(this.expression.getExpressionString()); - } - - @Test - public void postProcessContext() { - PathPatternMessageMatcher matcher = PathPatternMessageMatcher.withDefaults().matcher("/topics/{topic}/**"); - // @formatter:off - Message message = MessageBuilder.withPayload("M") - .setHeader(SimpMessageHeaderAccessor.DESTINATION_HEADER, "/topics/someTopic/sub1") - .build(); - // @formatter:on - EvaluationContext context = mock(EvaluationContext.class); - this.attribute = new MessageExpressionConfigAttribute(this.expression, matcher); - this.attribute.postProcess(context, message); - verify(context).setVariable("topic", "someTopic"); - } - -} diff --git a/access/src/test/java/org/springframework/security/messaging/access/expression/MessageExpressionVoterTests.java b/access/src/test/java/org/springframework/security/messaging/access/expression/MessageExpressionVoterTests.java deleted file mode 100644 index 624927a42e7..00000000000 --- a/access/src/test/java/org/springframework/security/messaging/access/expression/MessageExpressionVoterTests.java +++ /dev/null @@ -1,153 +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.messaging.access.expression; - -import java.util.Arrays; -import java.util.Collection; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.messaging.Message; -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.access.expression.SecurityExpressionHandler; -import org.springframework.security.core.Authentication; -import org.springframework.security.messaging.util.matcher.MessageMatcher; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -@ExtendWith(MockitoExtension.class) -public class MessageExpressionVoterTests { - - @Mock - Authentication authentication; - - @Mock - Message message; - - Collection attributes; - - @Mock - Expression expression; - - @Mock - MessageMatcher matcher; - - @Mock - SecurityExpressionHandler expressionHandler; - - @Mock - EvaluationContext evaluationContext; - - MessageExpressionVoter voter; - - @BeforeEach - public void setup() { - this.attributes = Arrays - .asList(new MessageExpressionConfigAttribute(this.expression, this.matcher)); - this.voter = new MessageExpressionVoter(); - } - - @Test - public void voteGranted() { - given(this.expression.getValue(any(EvaluationContext.class), eq(Boolean.class))).willReturn(true); - given(this.matcher.matcher(any())).willCallRealMethod(); - assertThat(this.voter.vote(this.authentication, this.message, this.attributes)) - .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); - } - - @Test - public void voteDenied() { - given(this.expression.getValue(any(EvaluationContext.class), eq(Boolean.class))).willReturn(false); - given(this.matcher.matcher(any())).willCallRealMethod(); - assertThat(this.voter.vote(this.authentication, this.message, this.attributes)) - .isEqualTo(AccessDecisionVoter.ACCESS_DENIED); - } - - @Test - public void voteAbstain() { - this.attributes = Arrays.asList(new SecurityConfig("ROLE_USER")); - assertThat(this.voter.vote(this.authentication, this.message, this.attributes)) - .isEqualTo(AccessDecisionVoter.ACCESS_ABSTAIN); - } - - @Test - public void supportsObjectClassFalse() { - assertThat(this.voter.supports(Object.class)).isFalse(); - } - - @Test - public void supportsMessageClassTrue() { - assertThat(this.voter.supports(Message.class)).isTrue(); - } - - @Test - public void supportsSecurityConfigFalse() { - assertThat(this.voter.supports(new SecurityConfig("ROLE_USER"))).isFalse(); - } - - @Test - public void supportsMessageExpressionConfigAttributeTrue() { - assertThat(this.voter.supports(new MessageExpressionConfigAttribute(this.expression, this.matcher))).isTrue(); - } - - @Test - public void setExpressionHandlerNull() { - assertThatIllegalArgumentException().isThrownBy(() -> this.voter.setExpressionHandler(null)); - } - - @Test - public void customExpressionHandler() { - this.voter.setExpressionHandler(this.expressionHandler); - given(this.expressionHandler.createEvaluationContext(this.authentication, this.message)) - .willReturn(this.evaluationContext); - given(this.expression.getValue(this.evaluationContext, Boolean.class)).willReturn(true); - given(this.matcher.matcher(any())).willCallRealMethod(); - assertThat(this.voter.vote(this.authentication, this.message, this.attributes)) - .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); - verify(this.expressionHandler).createEvaluationContext(this.authentication, this.message); - } - - @Test - public void postProcessEvaluationContext() { - final MessageExpressionConfigAttribute configAttribute = mock(MessageExpressionConfigAttribute.class); - this.voter.setExpressionHandler(this.expressionHandler); - given(this.expressionHandler.createEvaluationContext(this.authentication, this.message)) - .willReturn(this.evaluationContext); - given(configAttribute.getAuthorizeExpression()).willReturn(this.expression); - this.attributes = Arrays.asList(configAttribute); - given(configAttribute.postProcess(this.evaluationContext, this.message)).willReturn(this.evaluationContext); - given(this.expression.getValue(any(EvaluationContext.class), eq(Boolean.class))).willReturn(true); - assertThat(this.voter.vote(this.authentication, this.message, this.attributes)) - .isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); - verify(configAttribute).postProcess(this.evaluationContext, this.message); - } - -} diff --git a/access/src/test/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptorTests.java b/access/src/test/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptorTests.java deleted file mode 100644 index c584e0ac024..00000000000 --- a/access/src/test/java/org/springframework/security/messaging/access/intercept/ChannelSecurityInterceptorTests.java +++ /dev/null @@ -1,171 +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.messaging.access.intercept; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.security.access.AccessDecisionManager; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.access.intercept.RunAsManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willThrow; - -@ExtendWith(MockitoExtension.class) -public class ChannelSecurityInterceptorTests { - - @Mock - Message message; - - @Mock - MessageChannel channel; - - @Mock - MessageSecurityMetadataSource source; - - @Mock - AccessDecisionManager accessDecisionManager; - - @Mock - RunAsManager runAsManager; - - @Mock - Authentication runAs; - - Authentication originalAuth; - - List attrs; - - ChannelSecurityInterceptor interceptor; - - @BeforeEach - public void setup() { - this.attrs = Arrays.asList(new SecurityConfig("ROLE_USER")); - this.interceptor = new ChannelSecurityInterceptor(this.source); - this.interceptor.setAccessDecisionManager(this.accessDecisionManager); - this.interceptor.setRunAsManager(this.runAsManager); - this.originalAuth = new TestingAuthenticationToken("user", "pass", "ROLE_USER"); - SecurityContextHolder.getContext().setAuthentication(this.originalAuth); - } - - @AfterEach - public void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test - public void constructorMessageSecurityMetadataSourceNull() { - assertThatIllegalArgumentException().isThrownBy(() -> new ChannelSecurityInterceptor(null)); - } - - @Test - public void getSecureObjectClass() { - assertThat(this.interceptor.getSecureObjectClass()).isEqualTo(Message.class); - } - - @Test - public void obtainSecurityMetadataSource() { - assertThat(this.interceptor.obtainSecurityMetadataSource()).isEqualTo(this.source); - } - - @Test - public void preSendNullAttributes() { - assertThat(this.interceptor.preSend(this.message, this.channel)).isSameAs(this.message); - } - - @Test - public void preSendGrant() { - given(this.source.getAttributes(this.message)).willReturn(this.attrs); - Message result = this.interceptor.preSend(this.message, this.channel); - assertThat(result).isSameAs(this.message); - } - - @Test - public void preSendDeny() { - given(this.source.getAttributes(this.message)).willReturn(this.attrs); - willThrow(new AccessDeniedException("")).given(this.accessDecisionManager) - .decide(any(Authentication.class), eq(this.message), eq(this.attrs)); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.interceptor.preSend(this.message, this.channel)); - } - - @SuppressWarnings("unchecked") - @Test - public void preSendPostSendRunAs() { - given(this.source.getAttributes(this.message)).willReturn(this.attrs); - given(this.runAsManager.buildRunAs(any(Authentication.class), any(), any(Collection.class))) - .willReturn(this.runAs); - Message preSend = this.interceptor.preSend(this.message, this.channel); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.runAs); - this.interceptor.postSend(preSend, this.channel, true); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.originalAuth); - } - - @Test - public void afterSendCompletionNotTokenMessageNoExceptionThrown() { - this.interceptor.afterSendCompletion(this.message, this.channel, true, null); - } - - @SuppressWarnings("unchecked") - @Test - public void preSendFinallySendRunAs() { - given(this.source.getAttributes(this.message)).willReturn(this.attrs); - given(this.runAsManager.buildRunAs(any(Authentication.class), any(), any(Collection.class))) - .willReturn(this.runAs); - Message preSend = this.interceptor.preSend(this.message, this.channel); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.runAs); - this.interceptor.afterSendCompletion(preSend, this.channel, true, new RuntimeException()); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(this.originalAuth); - } - - @Test - public void preReceive() { - assertThat(this.interceptor.preReceive(this.channel)).isTrue(); - } - - @Test - public void postReceive() { - assertThat(this.interceptor.postReceive(this.message, this.channel)).isSameAs(this.message); - } - - @Test - public void afterReceiveCompletionNullExceptionNoExceptionThrown() { - this.interceptor.afterReceiveCompletion(this.message, this.channel, null); - } - -} diff --git a/access/src/test/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSourceTests.java b/access/src/test/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSourceTests.java deleted file mode 100644 index 686ee46515f..00000000000 --- a/access/src/test/java/org/springframework/security/messaging/access/intercept/DefaultMessageSecurityMetadataSourceTests.java +++ /dev/null @@ -1,101 +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.messaging.access.intercept; - -import java.util.Arrays; -import java.util.Collection; -import java.util.LinkedHashMap; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.messaging.Message; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.core.Authentication; -import org.springframework.security.messaging.util.matcher.MessageMatcher; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; - -@ExtendWith(MockitoExtension.class) -public class DefaultMessageSecurityMetadataSourceTests { - - @Mock - MessageMatcher matcher1; - - @Mock - MessageMatcher matcher2; - - @Mock - Message message; - - @Mock - Authentication authentication; - - SecurityConfig config1; - - SecurityConfig config2; - - LinkedHashMap, Collection> messageMap; - - MessageSecurityMetadataSource source; - - @BeforeEach - public void setup() { - this.messageMap = new LinkedHashMap<>(); - this.messageMap.put(this.matcher1, Arrays.asList(this.config1)); - this.messageMap.put(this.matcher2, Arrays.asList(this.config2)); - this.source = new DefaultMessageSecurityMetadataSource(this.messageMap); - } - - @Test - public void getAttributesEmpty() { - assertThat(this.source.getAttributes(this.message)).isEmpty(); - } - - @Test - public void getAttributesFirst() { - given(this.matcher1.matches(this.message)).willReturn(true); - assertThat(this.source.getAttributes(this.message)).containsOnly(this.config1); - } - - @Test - public void getAttributesSecond() { - given(this.matcher1.matches(this.message)).willReturn(true); - assertThat(this.source.getAttributes(this.message)).containsOnly(this.config2); - } - - @Test - public void getAllConfigAttributes() { - assertThat(this.source.getAllConfigAttributes()).containsOnly(this.config1, this.config2); - } - - @Test - public void supportsFalse() { - assertThat(this.source.supports(Object.class)).isFalse(); - } - - @Test - public void supportsTrue() { - assertThat(this.source.supports(Message.class)).isTrue(); - } - -} diff --git a/access/src/test/java/org/springframework/security/web/access/DefaultWebInvocationPrivilegeEvaluatorTests.java b/access/src/test/java/org/springframework/security/web/access/DefaultWebInvocationPrivilegeEvaluatorTests.java deleted file mode 100644 index 3dd7f12d8e1..00000000000 --- a/access/src/test/java/org/springframework/security/web/access/DefaultWebInvocationPrivilegeEvaluatorTests.java +++ /dev/null @@ -1,125 +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.web.access; - -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; -import org.mockito.BDDMockito; -import org.mockito.Mockito; - -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.mock.web.MockServletContext; -import org.springframework.security.access.AccessDecisionManager; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.intercept.RunAsManager; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; -import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; - -/** - * Tests - * {@link org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator}. - * - * @author Ben Alex - */ -public class DefaultWebInvocationPrivilegeEvaluatorTests { - - private AccessDecisionManager adm; - - private FilterInvocationSecurityMetadataSource ods; - - private RunAsManager ram; - - private FilterSecurityInterceptor interceptor; - - @BeforeEach - public final void setUp() { - this.interceptor = new FilterSecurityInterceptor(); - this.ods = Mockito.mock(FilterInvocationSecurityMetadataSource.class); - this.adm = Mockito.mock(AccessDecisionManager.class); - this.ram = Mockito.mock(RunAsManager.class); - this.interceptor.setAuthenticationManager(Mockito.mock(AuthenticationManager.class)); - this.interceptor.setSecurityMetadataSource(this.ods); - this.interceptor.setAccessDecisionManager(this.adm); - this.interceptor.setRunAsManager(this.ram); - this.interceptor.setApplicationEventPublisher(Mockito.mock(ApplicationEventPublisher.class)); - SecurityContextHolder.clearContext(); - } - - @Test - public void permitsAccessIfNoMatchingAttributesAndPublicInvocationsAllowed() { - DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor); - BDDMockito.given(this.ods.getAttributes(ArgumentMatchers.any())).willReturn(null); - Assertions.assertThat(wipe.isAllowed("/context", "/foo/index.jsp", "GET", Mockito.mock(Authentication.class))) - .isTrue(); - } - - @Test - public void deniesAccessIfNoMatchingAttributesAndPublicInvocationsNotAllowed() { - DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor); - BDDMockito.given(this.ods.getAttributes(ArgumentMatchers.any())).willReturn(null); - this.interceptor.setRejectPublicInvocations(true); - Assertions.assertThat(wipe.isAllowed("/context", "/foo/index.jsp", "GET", Mockito.mock(Authentication.class))) - .isFalse(); - } - - @Test - public void deniesAccessIfAuthenticationIsNull() { - DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor); - Assertions.assertThat(wipe.isAllowed("/foo/index.jsp", null)).isFalse(); - } - - @Test - public void allowsAccessIfAccessDecisionManagerDoes() { - Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX"); - DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor); - Assertions.assertThat(wipe.isAllowed("/foo/index.jsp", token)).isTrue(); - } - - @SuppressWarnings("unchecked") - @Test - public void deniesAccessIfAccessDecisionManagerDoes() { - Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX"); - DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor); - BDDMockito.willThrow(new AccessDeniedException("")) - .given(this.adm) - .decide(ArgumentMatchers.any(Authentication.class), ArgumentMatchers.any(), ArgumentMatchers.anyList()); - Assertions.assertThat(wipe.isAllowed("/foo/index.jsp", token)).isFalse(); - } - - @Test - public void isAllowedWhenServletContextIsSetThenPassedFilterInvocationHasServletContext() { - Authentication token = new TestingAuthenticationToken("test", "Password", "MOCK_INDEX"); - MockServletContext servletContext = new MockServletContext(); - ArgumentCaptor filterInvocationArgumentCaptor = ArgumentCaptor - .forClass(FilterInvocation.class); - DefaultWebInvocationPrivilegeEvaluator wipe = new DefaultWebInvocationPrivilegeEvaluator(this.interceptor); - wipe.setServletContext(servletContext); - wipe.isAllowed("/foo/index.jsp", token); - Mockito.verify(this.adm) - .decide(ArgumentMatchers.eq(token), filterInvocationArgumentCaptor.capture(), ArgumentMatchers.any()); - Assertions.assertThat(filterInvocationArgumentCaptor.getValue().getRequest().getServletContext()).isNotNull(); - } - -} diff --git a/access/src/test/java/org/springframework/security/web/access/channel/ChannelDecisionManagerImplTests.java b/access/src/test/java/org/springframework/security/web/access/channel/ChannelDecisionManagerImplTests.java deleted file mode 100644 index e550801303a..00000000000 --- a/access/src/test/java/org/springframework/security/web/access/channel/ChannelDecisionManagerImplTests.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.channel; - -import java.io.IOException; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.Vector; - -import jakarta.servlet.FilterChain; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.web.FilterInvocation; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.Mockito.mock; - -/** - * Tests {@link ChannelDecisionManagerImpl}. - * - * @author Ben Alex - */ -@SuppressWarnings("unchecked") -public class ChannelDecisionManagerImplTests { - - @Test - public void testCannotSetEmptyChannelProcessorsList() throws Exception { - ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); - assertThatIllegalArgumentException().isThrownBy(() -> { - cdm.setChannelProcessors(new Vector()); - cdm.afterPropertiesSet(); - }).withMessage("A list of ChannelProcessors is required"); - } - - @Test - public void testCannotSetIncorrectObjectTypesIntoChannelProcessorsList() { - ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); - List list = new Vector(); - list.add("THIS IS NOT A CHANNELPROCESSOR"); - assertThatIllegalArgumentException().isThrownBy(() -> cdm.setChannelProcessors(list)); - } - - @Test - public void testCannotSetNullChannelProcessorsList() throws Exception { - ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); - assertThatIllegalArgumentException().isThrownBy(() -> { - cdm.setChannelProcessors(null); - cdm.afterPropertiesSet(); - }).withMessage("A list of ChannelProcessors is required"); - } - - @Test - public void testDecideIsOperational() throws Exception { - ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); - MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false); - MockChannelProcessor cpAbc = new MockChannelProcessor("abc", true); - List list = new Vector(); - list.add(cpXyz); - list.add(cpAbc); - cdm.setChannelProcessors(list); - cdm.afterPropertiesSet(); - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class)); - List cad = SecurityConfig.createList("xyz"); - cdm.decide(fi, cad); - Assertions.assertThat(fi.getResponse().isCommitted()).isTrue(); - } - - @Test - public void testAnyChannelAttributeCausesProcessorsToBeSkipped() throws Exception { - ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); - MockChannelProcessor cpAbc = new MockChannelProcessor("abc", true); - List list = new Vector(); - list.add(cpAbc); - cdm.setChannelProcessors(list); - cdm.afterPropertiesSet(); - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class)); - cdm.decide(fi, SecurityConfig.createList(new String[] { "abc", "ANY_CHANNEL" })); - Assertions.assertThat(fi.getResponse().isCommitted()).isFalse(); - } - - @Test - public void testDecideIteratesAllProcessorsIfNoneCommitAResponse() throws Exception { - ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); - MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false); - MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false); - List list = new Vector(); - list.add(cpXyz); - list.add(cpAbc); - cdm.setChannelProcessors(list); - cdm.afterPropertiesSet(); - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class)); - cdm.decide(fi, SecurityConfig.createList("SOME_ATTRIBUTE_NO_PROCESSORS_SUPPORT")); - Assertions.assertThat(fi.getResponse().isCommitted()).isFalse(); - } - - @Test - public void testDelegatesSupports() throws Exception { - ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); - MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false); - MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false); - List list = new Vector(); - list.add(cpXyz); - list.add(cpAbc); - cdm.setChannelProcessors(list); - cdm.afterPropertiesSet(); - assertThat(cdm.supports(new SecurityConfig("xyz"))).isTrue(); - assertThat(cdm.supports(new SecurityConfig("abc"))).isTrue(); - assertThat(cdm.supports(new SecurityConfig("UNSUPPORTED"))).isFalse(); - } - - @Test - public void testGettersSetters() { - ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); - assertThat(cdm.getChannelProcessors()).isNull(); - MockChannelProcessor cpXyz = new MockChannelProcessor("xyz", false); - MockChannelProcessor cpAbc = new MockChannelProcessor("abc", false); - List list = new Vector(); - list.add(cpXyz); - list.add(cpAbc); - cdm.setChannelProcessors(list); - assertThat(cdm.getChannelProcessors()).isEqualTo(list); - } - - @Test - public void testStartupFailsWithEmptyChannelProcessorsList() throws Exception { - ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl(); - assertThatIllegalArgumentException().isThrownBy(cdm::afterPropertiesSet) - .withMessage("A list of ChannelProcessors is required"); - } - - private class MockChannelProcessor implements ChannelProcessor { - - private String configAttribute; - - private boolean failIfCalled; - - MockChannelProcessor(String configAttribute, boolean failIfCalled) { - this.configAttribute = configAttribute; - this.failIfCalled = failIfCalled; - } - - @Override - public void decide(FilterInvocation invocation, Collection config) throws IOException { - Iterator iter = config.iterator(); - if (this.failIfCalled) { - fail("Should not have called this channel processor: " + this.configAttribute); - } - while (iter.hasNext()) { - ConfigAttribute attr = (ConfigAttribute) iter.next(); - if (attr.getAttribute().equals(this.configAttribute)) { - invocation.getHttpResponse().sendRedirect("/redirected"); - return; - } - } - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return attribute.getAttribute().equals(this.configAttribute); - } - - } - -} diff --git a/access/src/test/java/org/springframework/security/web/access/channel/ChannelProcessingFilterTests.java b/access/src/test/java/org/springframework/security/web/access/channel/ChannelProcessingFilterTests.java deleted file mode 100644 index d486d361894..00000000000 --- a/access/src/test/java/org/springframework/security/web/access/channel/ChannelProcessingFilterTests.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.channel; - -import java.io.IOException; -import java.util.Collection; - -import jakarta.servlet.FilterChain; -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; -import org.springframework.security.web.servlet.TestMockHttpServletRequests; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.Mockito.mock; - -/** - * Tests {@link ChannelProcessingFilter}. - * - * @author Ben Alex - */ -public class ChannelProcessingFilterTests { - - @Test - public void testDetectsMissingChannelDecisionManager() { - ChannelProcessingFilter filter = new ChannelProcessingFilter(); - MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "MOCK"); - filter.setSecurityMetadataSource(fids); - assertThatIllegalArgumentException().isThrownBy(filter::afterPropertiesSet); - } - - @Test - public void testDetectsMissingFilterInvocationSecurityMetadataSource() { - ChannelProcessingFilter filter = new ChannelProcessingFilter(); - filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "MOCK")); - assertThatIllegalArgumentException().isThrownBy(filter::afterPropertiesSet); - } - - @Test - public void testDetectsSupportedConfigAttribute() { - ChannelProcessingFilter filter = new ChannelProcessingFilter(); - filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "SUPPORTS_MOCK_ONLY")); - MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, - "SUPPORTS_MOCK_ONLY"); - filter.setSecurityMetadataSource(fids); - filter.afterPropertiesSet(); - } - - @Test - public void testDetectsUnsupportedConfigAttribute() { - ChannelProcessingFilter filter = new ChannelProcessingFilter(); - filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "SUPPORTS_MOCK_ONLY")); - MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, - "SUPPORTS_MOCK_ONLY", "INVALID_ATTRIBUTE"); - filter.setSecurityMetadataSource(fids); - assertThatIllegalArgumentException().isThrownBy(filter::afterPropertiesSet); - } - - @Test - public void testDoFilterWhenManagerDoesCommitResponse() throws Exception { - ChannelProcessingFilter filter = new ChannelProcessingFilter(); - filter.setChannelDecisionManager(new MockChannelDecisionManager(true, "SOME_ATTRIBUTE")); - MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "SOME_ATTRIBUTE"); - filter.setSecurityMetadataSource(fids); - MockHttpServletRequest request = TestMockHttpServletRequests.get("/path").build(); - request.setQueryString("info=now"); - MockHttpServletResponse response = new MockHttpServletResponse(); - filter.doFilter(request, response, mock(FilterChain.class)); - } - - @Test - public void testDoFilterWhenManagerDoesNotCommitResponse() throws Exception { - ChannelProcessingFilter filter = new ChannelProcessingFilter(); - filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "SOME_ATTRIBUTE")); - MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "SOME_ATTRIBUTE"); - filter.setSecurityMetadataSource(fids); - MockHttpServletRequest request = TestMockHttpServletRequests.get("/path").build(); - request.setQueryString("info=now"); - MockHttpServletResponse response = new MockHttpServletResponse(); - filter.doFilter(request, response, mock(FilterChain.class)); - } - - @Test - public void testDoFilterWhenNullConfigAttributeReturned() throws Exception { - ChannelProcessingFilter filter = new ChannelProcessingFilter(); - filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "NOT_USED")); - MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", true, "NOT_USED"); - filter.setSecurityMetadataSource(fids); - MockHttpServletRequest request = TestMockHttpServletRequests.get("/PATH_NOT_MATCHING_CONFIG_ATTRIBUTE").build(); - request.setQueryString("info=now"); - MockHttpServletResponse response = new MockHttpServletResponse(); - filter.doFilter(request, response, mock(FilterChain.class)); - } - - @Test - public void testGetterSetters() { - ChannelProcessingFilter filter = new ChannelProcessingFilter(); - filter.setChannelDecisionManager(new MockChannelDecisionManager(false, "MOCK")); - assertThat(filter.getChannelDecisionManager() != null).isTrue(); - MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path", false, "MOCK"); - filter.setSecurityMetadataSource(fids); - assertThat(filter.getSecurityMetadataSource()).isSameAs(fids); - filter.afterPropertiesSet(); - } - - private class MockChannelDecisionManager implements ChannelDecisionManager { - - private String supportAttribute; - - private boolean commitAResponse; - - MockChannelDecisionManager(boolean commitAResponse, String supportAttribute) { - this.commitAResponse = commitAResponse; - this.supportAttribute = supportAttribute; - } - - @Override - public void decide(FilterInvocation invocation, Collection config) throws IOException { - if (this.commitAResponse) { - invocation.getHttpResponse().sendRedirect("/redirected"); - } - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return attribute.getAttribute().equals(this.supportAttribute); - } - - } - - private class MockFilterInvocationDefinitionMap implements FilterInvocationSecurityMetadataSource { - - private Collection toReturn; - - private String servletPath; - - private boolean provideIterator; - - MockFilterInvocationDefinitionMap(String servletPath, boolean provideIterator, String... toReturn) { - this.servletPath = servletPath; - this.toReturn = SecurityConfig.createList(toReturn); - this.provideIterator = provideIterator; - } - - @Override - public Collection getAttributes(Object object) throws IllegalArgumentException { - FilterInvocation fi = (FilterInvocation) object; - if (this.servletPath.equals(fi.getHttpRequest().getServletPath())) { - return this.toReturn; - } - else { - return null; - } - } - - @Override - public Collection getAllConfigAttributes() { - if (!this.provideIterator) { - return null; - } - return this.toReturn; - } - - @Override - public boolean supports(Class clazz) { - return true; - } - - } - -} diff --git a/access/src/test/java/org/springframework/security/web/access/channel/InsecureChannelProcessorTests.java b/access/src/test/java/org/springframework/security/web/access/channel/InsecureChannelProcessorTests.java deleted file mode 100644 index 6b4f1d8bae1..00000000000 --- a/access/src/test/java/org/springframework/security/web/access/channel/InsecureChannelProcessorTests.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.channel; - -import jakarta.servlet.FilterChain; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.servlet.TestMockHttpServletRequests; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.Mockito.mock; - -/** - * Tests {@link InsecureChannelProcessor}. - * - * @author Ben Alex - */ -public class InsecureChannelProcessorTests { - - @Test - public void testDecideDetectsAcceptableChannel() throws Exception { - MockHttpServletRequest request = TestMockHttpServletRequests.get("http://localhost:8080") - .requestUri("/bigapp", "/servlet", null) - .queryString("info=true") - .build(); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class)); - InsecureChannelProcessor processor = new InsecureChannelProcessor(); - processor.decide(fi, SecurityConfig.createList("SOME_IGNORED_ATTRIBUTE", "REQUIRES_INSECURE_CHANNEL")); - Assertions.assertThat(fi.getResponse().isCommitted()).isFalse(); - } - - @Test - public void testDecideDetectsUnacceptableChannel() throws Exception { - MockHttpServletRequest request = TestMockHttpServletRequests.get("https://localhost:8443") - .requestUri("/bigapp", "/servlet", null) - .queryString("info=true") - .build(); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class)); - InsecureChannelProcessor processor = new InsecureChannelProcessor(); - processor.decide(fi, - SecurityConfig.createList(new String[] { "SOME_IGNORED_ATTRIBUTE", "REQUIRES_INSECURE_CHANNEL" })); - Assertions.assertThat(fi.getResponse().isCommitted()).isTrue(); - } - - @Test - public void testDecideRejectsNulls() throws Exception { - InsecureChannelProcessor processor = new InsecureChannelProcessor(); - processor.afterPropertiesSet(); - assertThatIllegalArgumentException().isThrownBy(() -> processor.decide(null, null)); - } - - @Test - public void testGettersSetters() { - InsecureChannelProcessor processor = new InsecureChannelProcessor(); - assertThat(processor.getInsecureKeyword()).isEqualTo("REQUIRES_INSECURE_CHANNEL"); - processor.setInsecureKeyword("X"); - assertThat(processor.getInsecureKeyword()).isEqualTo("X"); - assertThat(processor.getEntryPoint() != null).isTrue(); - processor.setEntryPoint(null); - assertThat(processor.getEntryPoint() == null).isTrue(); - } - - @Test - public void testMissingEntryPoint() throws Exception { - InsecureChannelProcessor processor = new InsecureChannelProcessor(); - processor.setEntryPoint(null); - assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet) - .withMessage("entryPoint required"); - } - - @Test - public void testMissingSecureChannelKeyword() throws Exception { - InsecureChannelProcessor processor = new InsecureChannelProcessor(); - processor.setInsecureKeyword(null); - assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet) - .withMessage("insecureKeyword required"); - processor.setInsecureKeyword(""); - assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet) - .withMessage("insecureKeyword required"); - } - - @Test - public void testSupports() { - InsecureChannelProcessor processor = new InsecureChannelProcessor(); - assertThat(processor.supports(new SecurityConfig("REQUIRES_INSECURE_CHANNEL"))).isTrue(); - assertThat(processor.supports(null)).isFalse(); - assertThat(processor.supports(new SecurityConfig("NOT_SUPPORTED"))).isFalse(); - } - -} diff --git a/access/src/test/java/org/springframework/security/web/access/channel/RetryWithHttpEntryPointTests.java b/access/src/test/java/org/springframework/security/web/access/channel/RetryWithHttpEntryPointTests.java deleted file mode 100644 index ce995e6bdcb..00000000000 --- a/access/src/test/java/org/springframework/security/web/access/channel/RetryWithHttpEntryPointTests.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.channel; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.web.PortMapper; -import org.springframework.security.web.PortMapperImpl; -import org.springframework.security.web.RedirectStrategy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.Mockito.mock; - -/** - * Tests {@link RetryWithHttpEntryPoint}. - * - * @author Ben Alex - */ -public class RetryWithHttpEntryPointTests { - - @Test - public void testDetectsMissingPortMapper() { - RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint(); - assertThatIllegalArgumentException().isThrownBy(() -> ep.setPortMapper(null)); - } - - @Test - public void testGettersSetters() { - RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint(); - PortMapper portMapper = mock(PortMapper.class); - RedirectStrategy redirector = mock(RedirectStrategy.class); - ep.setPortMapper(portMapper); - ep.setRedirectStrategy(redirector); - assertThat(ep.getPortMapper()).isSameAs(portMapper); - assertThat(ep.getRedirectStrategy()).isSameAs(redirector); - } - - @Test - public void testNormalOperation() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html"); - request.setQueryString("open=true"); - request.setScheme("https"); - request.setServerName("localhost"); - request.setServerPort(443); - MockHttpServletResponse response = new MockHttpServletResponse(); - RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint(); - ep.setPortMapper(new PortMapperImpl()); - ep.commence(request, response); - assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/bigWebApp/hello/pathInfo.html?open=true"); - } - - @Test - public void testNormalOperationWithNullQueryString() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello"); - request.setScheme("https"); - request.setServerName("localhost"); - request.setServerPort(443); - MockHttpServletResponse response = new MockHttpServletResponse(); - RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint(); - ep.setPortMapper(new PortMapperImpl()); - ep.commence(request, response); - assertThat(response.getRedirectedUrl()).isEqualTo("http://localhost/bigWebApp/hello"); - } - - @Test - public void testOperationWhenTargetPortIsUnknown() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp"); - request.setQueryString("open=true"); - request.setScheme("https"); - request.setServerName("www.example.com"); - request.setServerPort(8768); - MockHttpServletResponse response = new MockHttpServletResponse(); - RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint(); - ep.setPortMapper(new PortMapperImpl()); - ep.commence(request, response); - assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp?open=true"); - } - - @Test - public void testOperationWithNonStandardPort() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html"); - request.setQueryString("open=true"); - request.setScheme("https"); - request.setServerName("localhost"); - request.setServerPort(9999); - MockHttpServletResponse response = new MockHttpServletResponse(); - PortMapperImpl portMapper = new PortMapperImpl(); - Map map = new HashMap<>(); - map.put("8888", "9999"); - portMapper.setPortMappings(map); - RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint(); - ep.setPortMapper(portMapper); - ep.commence(request, response); - assertThat(response.getRedirectedUrl()) - .isEqualTo("http://localhost:8888/bigWebApp/hello/pathInfo.html?open=true"); - } - -} diff --git a/access/src/test/java/org/springframework/security/web/access/channel/RetryWithHttpsEntryPointTests.java b/access/src/test/java/org/springframework/security/web/access/channel/RetryWithHttpsEntryPointTests.java deleted file mode 100644 index ccf627ed42c..00000000000 --- a/access/src/test/java/org/springframework/security/web/access/channel/RetryWithHttpsEntryPointTests.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.channel; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.web.PortMapperImpl; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests {@link RetryWithHttpsEntryPoint}. - * - * @author Ben Alex - */ -public class RetryWithHttpsEntryPointTests { - - @Test - public void testDetectsMissingPortMapper() { - RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint(); - assertThatIllegalArgumentException().isThrownBy(() -> ep.setPortMapper(null)); - } - - @Test - public void testGettersSetters() { - RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint(); - ep.setPortMapper(new PortMapperImpl()); - assertThat(ep.getPortMapper() != null).isTrue(); - } - - @Test - public void testNormalOperation() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html"); - request.setQueryString("open=true"); - request.setScheme("http"); - request.setServerName("www.example.com"); - request.setServerPort(80); - MockHttpServletResponse response = new MockHttpServletResponse(); - RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint(); - ep.setPortMapper(new PortMapperImpl()); - ep.commence(request, response); - assertThat(response.getRedirectedUrl()) - .isEqualTo("https://www.example.com/bigWebApp/hello/pathInfo.html?open=true"); - } - - @Test - public void testNormalOperationWithNullQueryString() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello"); - request.setScheme("http"); - request.setServerName("www.example.com"); - request.setServerPort(80); - MockHttpServletResponse response = new MockHttpServletResponse(); - RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint(); - ep.setPortMapper(new PortMapperImpl()); - ep.commence(request, response); - assertThat(response.getRedirectedUrl()).isEqualTo("https://www.example.com/bigWebApp/hello"); - } - - @Test - public void testOperationWhenTargetPortIsUnknown() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp"); - request.setQueryString("open=true"); - request.setScheme("http"); - request.setServerName("www.example.com"); - request.setServerPort(8768); - MockHttpServletResponse response = new MockHttpServletResponse(); - RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint(); - ep.setPortMapper(new PortMapperImpl()); - ep.commence(request, response); - assertThat(response.getRedirectedUrl()).isEqualTo("/bigWebApp?open=true"); - } - - @Test - public void testOperationWithNonStandardPort() throws Exception { - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bigWebApp/hello/pathInfo.html"); - request.setQueryString("open=true"); - request.setScheme("http"); - request.setServerName("www.example.com"); - request.setServerPort(8888); - MockHttpServletResponse response = new MockHttpServletResponse(); - PortMapperImpl portMapper = new PortMapperImpl(); - Map map = new HashMap<>(); - map.put("8888", "9999"); - portMapper.setPortMappings(map); - RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint(); - ep.setPortMapper(portMapper); - ep.commence(request, response); - assertThat(response.getRedirectedUrl()) - .isEqualTo("https://www.example.com:9999/bigWebApp/hello/pathInfo.html?open=true"); - } - -} diff --git a/access/src/test/java/org/springframework/security/web/access/channel/SecureChannelProcessorTests.java b/access/src/test/java/org/springframework/security/web/access/channel/SecureChannelProcessorTests.java deleted file mode 100644 index f67d0ae7b39..00000000000 --- a/access/src/test/java/org/springframework/security/web/access/channel/SecureChannelProcessorTests.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.channel; - -import jakarta.servlet.FilterChain; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.servlet.TestMockHttpServletRequests; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.Mockito.mock; - -/** - * Tests {@link SecureChannelProcessor}. - * - * @author Ben Alex - */ -public class SecureChannelProcessorTests { - - @Test - public void testDecideDetectsAcceptableChannel() throws Exception { - MockHttpServletRequest request = TestMockHttpServletRequests.get("https://localhost:8443") - .requestUri("/bigapp", "/servlet", null) - .queryString("info=true") - .build(); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class)); - SecureChannelProcessor processor = new SecureChannelProcessor(); - processor.decide(fi, SecurityConfig.createList("SOME_IGNORED_ATTRIBUTE", "REQUIRES_SECURE_CHANNEL")); - Assertions.assertThat(fi.getResponse().isCommitted()).isFalse(); - } - - @Test - public void testDecideDetectsUnacceptableChannel() throws Exception { - MockHttpServletRequest request = TestMockHttpServletRequests.get("http://localhost:8080") - .requestUri("/bigapp", "/servlet", null) - .queryString("info=true") - .build(); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterInvocation fi = new FilterInvocation(request, response, mock(FilterChain.class)); - SecureChannelProcessor processor = new SecureChannelProcessor(); - processor.decide(fi, - SecurityConfig.createList(new String[] { "SOME_IGNORED_ATTRIBUTE", "REQUIRES_SECURE_CHANNEL" })); - Assertions.assertThat(fi.getResponse().isCommitted()).isTrue(); - } - - @Test - public void testDecideRejectsNulls() throws Exception { - SecureChannelProcessor processor = new SecureChannelProcessor(); - processor.afterPropertiesSet(); - assertThatIllegalArgumentException().isThrownBy(() -> processor.decide(null, null)); - } - - @Test - public void testGettersSetters() { - SecureChannelProcessor processor = new SecureChannelProcessor(); - assertThat(processor.getSecureKeyword()).isEqualTo("REQUIRES_SECURE_CHANNEL"); - processor.setSecureKeyword("X"); - assertThat(processor.getSecureKeyword()).isEqualTo("X"); - assertThat(processor.getEntryPoint() != null).isTrue(); - processor.setEntryPoint(null); - assertThat(processor.getEntryPoint() == null).isTrue(); - } - - @Test - public void testMissingEntryPoint() throws Exception { - SecureChannelProcessor processor = new SecureChannelProcessor(); - processor.setEntryPoint(null); - assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet) - .withMessage("entryPoint required"); - } - - @Test - public void testMissingSecureChannelKeyword() throws Exception { - SecureChannelProcessor processor = new SecureChannelProcessor(); - processor.setSecureKeyword(null); - assertThatIllegalArgumentException().isThrownBy(processor::afterPropertiesSet) - .withMessage("secureKeyword required"); - processor.setSecureKeyword(""); - assertThatIllegalArgumentException().isThrownBy(() -> processor.afterPropertiesSet()) - .withMessage("secureKeyword required"); - } - - @Test - public void testSupports() { - SecureChannelProcessor processor = new SecureChannelProcessor(); - assertThat(processor.supports(new SecurityConfig("REQUIRES_SECURE_CHANNEL"))).isTrue(); - assertThat(processor.supports(null)).isFalse(); - assertThat(processor.supports(new SecurityConfig("NOT_SUPPORTED"))).isFalse(); - } - -} diff --git a/access/src/test/java/org/springframework/security/web/access/expression/DefaultWebSecurityExpressionHandlerTests.java b/access/src/test/java/org/springframework/security/web/access/expression/DefaultWebSecurityExpressionHandlerTests.java deleted file mode 100644 index 361812397a4..00000000000 --- a/access/src/test/java/org/springframework/security/web/access/expression/DefaultWebSecurityExpressionHandlerTests.java +++ /dev/null @@ -1,94 +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.web.access.expression; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.beans.factory.support.RootBeanDefinition; -import org.springframework.context.support.StaticApplicationContext; -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.expression.ExpressionParser; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.authentication.AuthenticationTrustResolver; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.FilterInvocation; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -@ExtendWith(MockitoExtension.class) -public class DefaultWebSecurityExpressionHandlerTests { - - @Mock - private AuthenticationTrustResolver trustResolver; - - @Mock - private Authentication authentication; - - @Mock - private FilterInvocation invocation; - - private DefaultWebSecurityExpressionHandler handler; - - @BeforeEach - public void setup() { - this.handler = new DefaultWebSecurityExpressionHandler(); - } - - @AfterEach - public void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test - public void expressionPropertiesAreResolvedAgainstAppContextBeans() { - StaticApplicationContext appContext = new StaticApplicationContext(); - RootBeanDefinition bean = new RootBeanDefinition(SecurityConfig.class); - bean.getConstructorArgumentValues().addGenericArgumentValue("ROLE_A"); - appContext.registerBeanDefinition("role", bean); - this.handler.setApplicationContext(appContext); - EvaluationContext ctx = this.handler.createEvaluationContext(mock(Authentication.class), - mock(FilterInvocation.class)); - ExpressionParser parser = this.handler.getExpressionParser(); - assertThat(parser.parseExpression("@role.getAttribute() == 'ROLE_A'").getValue(ctx, Boolean.class)).isTrue(); - assertThat(parser.parseExpression("@role.attribute == 'ROLE_A'").getValue(ctx, Boolean.class)).isTrue(); - } - - @Test - public void setTrustResolverNull() { - assertThatIllegalArgumentException().isThrownBy(() -> this.handler.setTrustResolver(null)); - } - - @Test - public void createEvaluationContextCustomTrustResolver() { - this.handler.setTrustResolver(this.trustResolver); - Expression expression = this.handler.getExpressionParser().parseExpression("anonymous"); - EvaluationContext context = this.handler.createEvaluationContext(this.authentication, this.invocation); - assertThat(expression.getValue(context, Boolean.class)).isFalse(); - verify(this.trustResolver).isAnonymous(this.authentication); - } - -} diff --git a/access/src/test/java/org/springframework/security/web/access/expression/ExpressionBasedFilterInvocationSecurityMetadataSourceTests.java b/access/src/test/java/org/springframework/security/web/access/expression/ExpressionBasedFilterInvocationSecurityMetadataSourceTests.java deleted file mode 100644 index 509e32f6c6e..00000000000 --- a/access/src/test/java/org/springframework/security/web/access/expression/ExpressionBasedFilterInvocationSecurityMetadataSourceTests.java +++ /dev/null @@ -1,63 +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.web.access.expression; - -import java.util.Collection; -import java.util.LinkedHashMap; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.util.matcher.AnyRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * @author Luke Taylor - */ -public class ExpressionBasedFilterInvocationSecurityMetadataSourceTests { - - @Test - public void expectedAttributeIsReturned() { - final String expression = "hasRole('X')"; - LinkedHashMap> requestMap = new LinkedHashMap<>(); - requestMap.put(AnyRequestMatcher.INSTANCE, SecurityConfig.createList(expression)); - ExpressionBasedFilterInvocationSecurityMetadataSource mds = new ExpressionBasedFilterInvocationSecurityMetadataSource( - requestMap, new DefaultWebSecurityExpressionHandler()); - assertThat(mds.getAllConfigAttributes()).hasSize(1); - Collection attrs = mds.getAttributes(new FilterInvocation("/path", "GET")); - assertThat(attrs).hasSize(1); - WebExpressionConfigAttribute attribute = (WebExpressionConfigAttribute) attrs.toArray()[0]; - assertThat(attribute.getAttribute()).isNull(); - assertThat(attribute.getAuthorizeExpression().getExpressionString()).isEqualTo(expression); - assertThat(attribute.toString()).isEqualTo(expression); - } - - @Test - public void invalidExpressionIsRejected() { - LinkedHashMap> requestMap = new LinkedHashMap<>(); - requestMap.put(AnyRequestMatcher.INSTANCE, SecurityConfig.createList("hasRole('X'")); - assertThatIllegalArgumentException() - .isThrownBy(() -> new ExpressionBasedFilterInvocationSecurityMetadataSource(requestMap, - new DefaultWebSecurityExpressionHandler())); - } - -} diff --git a/access/src/test/java/org/springframework/security/web/access/expression/WebExpressionVoterTests.java b/access/src/test/java/org/springframework/security/web/access/expression/WebExpressionVoterTests.java deleted file mode 100644 index 206f3c4b42b..00000000000 --- a/access/src/test/java/org/springframework/security/web/access/expression/WebExpressionVoterTests.java +++ /dev/null @@ -1,116 +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.web.access.expression; - -import java.util.ArrayList; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import org.aopalliance.intercept.MethodInvocation; -import org.junit.jupiter.api.Test; - -import org.springframework.expression.EvaluationContext; -import org.springframework.expression.Expression; -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.access.expression.SecurityExpressionHandler; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.FilterInvocation; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * @author Luke Taylor - */ -@SuppressWarnings({ "unchecked" }) -public class WebExpressionVoterTests { - - private Authentication user = new TestingAuthenticationToken("user", "pass", "X"); - - @Test - public void supportsWebConfigAttributeAndFilterInvocation() { - WebExpressionVoter voter = new WebExpressionVoter(); - assertThat(voter.supports( - new WebExpressionConfigAttribute(mock(Expression.class), mock(EvaluationContextPostProcessor.class)))) - .isTrue(); - assertThat(voter.supports(FilterInvocation.class)).isTrue(); - assertThat(voter.supports(MethodInvocation.class)).isFalse(); - } - - @Test - public void abstainsIfNoAttributeFound() { - WebExpressionVoter voter = new WebExpressionVoter(); - assertThat( - voter.vote(this.user, new FilterInvocation("/path", "GET"), SecurityConfig.createList("A", "B", "C"))) - .isEqualTo(AccessDecisionVoter.ACCESS_ABSTAIN); - } - - @Test - public void grantsAccessIfExpressionIsTrueDeniesIfFalse() { - WebExpressionVoter voter = new WebExpressionVoter(); - Expression ex = mock(Expression.class); - EvaluationContextPostProcessor postProcessor = mock(EvaluationContextPostProcessor.class); - given(postProcessor.postProcess(any(EvaluationContext.class), any(FilterInvocation.class))) - .willAnswer((invocation) -> invocation.getArgument(0)); - WebExpressionConfigAttribute weca = new WebExpressionConfigAttribute(ex, postProcessor); - EvaluationContext ctx = mock(EvaluationContext.class); - SecurityExpressionHandler eh = mock(SecurityExpressionHandler.class); - FilterInvocation fi = new FilterInvocation("/path", "GET"); - voter.setExpressionHandler(eh); - given(eh.createEvaluationContext(this.user, fi)).willReturn(ctx); - given(ex.getValue(ctx, Boolean.class)).willReturn(Boolean.TRUE, Boolean.FALSE); - ArrayList attributes = new ArrayList(); - attributes.addAll(SecurityConfig.createList("A", "B", "C")); - attributes.add(weca); - assertThat(voter.vote(this.user, fi, attributes)).isEqualTo(AccessDecisionVoter.ACCESS_GRANTED); - // Second time false - assertThat(voter.vote(this.user, fi, attributes)).isEqualTo(AccessDecisionVoter.ACCESS_DENIED); - } - - // SEC-2507 - @Test - public void supportFilterInvocationSubClass() { - WebExpressionVoter voter = new WebExpressionVoter(); - assertThat(voter.supports(FilterInvocationChild.class)).isTrue(); - } - - @Test - public void supportFilterInvocation() { - WebExpressionVoter voter = new WebExpressionVoter(); - assertThat(voter.supports(FilterInvocation.class)).isTrue(); - } - - @Test - public void supportsObjectIsFalse() { - WebExpressionVoter voter = new WebExpressionVoter(); - assertThat(voter.supports(Object.class)).isFalse(); - } - - private static class FilterInvocationChild extends FilterInvocation { - - FilterInvocationChild(ServletRequest request, ServletResponse response, FilterChain chain) { - super(request, response, chain); - } - - } - -} diff --git a/access/src/test/java/org/springframework/security/web/access/intercept/DefaultFilterInvocationSecurityMetadataSourceTests.java b/access/src/test/java/org/springframework/security/web/access/intercept/DefaultFilterInvocationSecurityMetadataSourceTests.java deleted file mode 100644 index 3de762fd5ee..00000000000 --- a/access/src/test/java/org/springframework/security/web/access/intercept/DefaultFilterInvocationSecurityMetadataSourceTests.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.intercept; - -import java.util.Collection; -import java.util.LinkedHashMap; - -import jakarta.servlet.FilterChain; -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpMethod; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.servlet.TestMockHttpServletRequests; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -/** - * Tests {@link DefaultFilterInvocationSecurityMetadataSource}. - * - * @author Ben Alex - */ -public class DefaultFilterInvocationSecurityMetadataSourceTests { - - private DefaultFilterInvocationSecurityMetadataSource fids; - - private Collection def = SecurityConfig.createList("ROLE_ONE"); - - private void createFids(String pattern, HttpMethod method) { - LinkedHashMap> requestMap = new LinkedHashMap<>(); - requestMap.put(PathPatternRequestMatcher.pathPattern(method, pattern), this.def); - this.fids = new DefaultFilterInvocationSecurityMetadataSource(requestMap); - } - - @Test - public void lookupNotRequiringExactMatchSucceedsIfNotMatching() { - createFids("/secure/super/**", null); - FilterInvocation fi = createFilterInvocation("/secure/super/somefile.html", null, null, "GET"); - assertThat(this.fids.getAttributes(fi)).isEqualTo(this.def); - } - - /** - * SEC-501. Note that as of 2.0, lower case comparisons are the default for this - * class. - */ - @Test - public void lookupNotRequiringExactMatchSucceedsIfSecureUrlPathContainsUpperCase() { - createFids("/secure/super/**", null); - FilterInvocation fi = createFilterInvocation("/secure", "/super/somefile.html", null, "GET"); - Collection response = this.fids.getAttributes(fi); - assertThat(response).isEqualTo(this.def); - } - - @Test - public void lookupRequiringExactMatchIsSuccessful() { - createFids("/SeCurE/super/**", null); - FilterInvocation fi = createFilterInvocation("/SeCurE/super/somefile.html", null, null, "GET"); - Collection response = this.fids.getAttributes(fi); - assertThat(response).isEqualTo(this.def); - } - - @Test - public void lookupRequiringExactMatchWithAdditionalSlashesIsSuccessful() { - createFids("/someAdminPage.html**", null); - FilterInvocation fi = createFilterInvocation("/someAdminPage.html", null, "a=/test", "GET"); - Collection response = this.fids.getAttributes(fi); - assertThat(response); // see SEC-161 (it should truncate after ? - // sign).isEqualTo(def) - } - - @Test - public void httpMethodLookupSucceeds() { - createFids("/somepage**", HttpMethod.GET); - FilterInvocation fi = createFilterInvocation("/somepage", null, null, "GET"); - Collection attrs = this.fids.getAttributes(fi); - assertThat(attrs).isEqualTo(this.def); - } - - @Test - public void generalMatchIsUsedIfNoMethodSpecificMatchExists() { - createFids("/somepage**", null); - FilterInvocation fi = createFilterInvocation("/somepage", null, null, "GET"); - Collection attrs = this.fids.getAttributes(fi); - assertThat(attrs).isEqualTo(this.def); - } - - @Test - public void requestWithDifferentHttpMethodDoesntMatch() { - createFids("/somepage**", HttpMethod.GET); - FilterInvocation fi = createFilterInvocation("/somepage", null, null, "POST"); - Collection attrs = this.fids.getAttributes(fi); - assertThat(attrs).isEmpty(); - } - - // SEC-1236 - @Test - public void mixingPatternsWithAndWithoutHttpMethodsIsSupported() { - LinkedHashMap> requestMap = new LinkedHashMap<>(); - Collection userAttrs = SecurityConfig.createList("A"); - requestMap.put(PathPatternRequestMatcher.pathPattern("/user/**"), userAttrs); - requestMap.put(PathPatternRequestMatcher.pathPattern(HttpMethod.GET, "/teller/**"), - SecurityConfig.createList("B")); - this.fids = new DefaultFilterInvocationSecurityMetadataSource(requestMap); - FilterInvocation fi = createFilterInvocation("/user", null, null, "GET"); - Collection attrs = this.fids.getAttributes(fi); - assertThat(attrs).isEqualTo(userAttrs); - } - - /** - * Check fixes for SEC-321 - */ - @Test - public void extraQuestionMarkStillMatches() { - createFids("/someAdminPage.html*", null); - FilterInvocation fi = createFilterInvocation("/someAdminPage.html", null, null, "GET"); - Collection response = this.fids.getAttributes(fi); - assertThat(response).isEqualTo(this.def); - fi = createFilterInvocation("/someAdminPage.html", null, "?", "GET"); - response = this.fids.getAttributes(fi); - assertThat(response).isEqualTo(this.def); - } - - private FilterInvocation createFilterInvocation(String servletPath, String pathInfo, String queryString, - String method) { - MockHttpServletRequest request = TestMockHttpServletRequests.request(method) - .requestUri(null, servletPath, pathInfo) - .queryString(queryString) - .build(); - return new FilterInvocation(request, new MockHttpServletResponse(), mock(FilterChain.class)); - } - -} diff --git a/access/src/test/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptorTests.java b/access/src/test/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptorTests.java deleted file mode 100644 index 5194c383258..00000000000 --- a/access/src/test/java/org/springframework/security/web/access/intercept/FilterSecurityInterceptorTests.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.web.access.intercept; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.context.ApplicationEventPublisher; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.access.AccessDecisionManager; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.access.event.AuthorizedEvent; -import org.springframework.security.access.intercept.AfterInvocationManager; -import org.springframework.security.access.intercept.RunAsManager; -import org.springframework.security.access.intercept.RunAsUserToken; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.servlet.TestMockHttpServletRequests; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyCollection; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -/** - * Tests {@link FilterSecurityInterceptor}. - * - * @author Ben Alex - * @author Luke Taylor - * @author Rob Winch - */ -public class FilterSecurityInterceptorTests { - - private AuthenticationManager am; - - private AccessDecisionManager adm; - - private FilterInvocationSecurityMetadataSource ods; - - private RunAsManager ram; - - private FilterSecurityInterceptor interceptor; - - private ApplicationEventPublisher publisher; - - @BeforeEach - public final void setUp() { - this.interceptor = new FilterSecurityInterceptor(); - this.am = mock(AuthenticationManager.class); - this.ods = mock(FilterInvocationSecurityMetadataSource.class); - this.adm = mock(AccessDecisionManager.class); - this.ram = mock(RunAsManager.class); - this.publisher = mock(ApplicationEventPublisher.class); - this.interceptor.setAuthenticationManager(this.am); - this.interceptor.setSecurityMetadataSource(this.ods); - this.interceptor.setAccessDecisionManager(this.adm); - this.interceptor.setRunAsManager(this.ram); - this.interceptor.setApplicationEventPublisher(this.publisher); - SecurityContextHolder.clearContext(); - } - - @AfterEach - public void tearDown() { - SecurityContextHolder.clearContext(); - } - - @Test - public void testEnsuresAccessDecisionManagerSupportsFilterInvocationClass() throws Exception { - given(this.adm.supports(FilterInvocation.class)).willReturn(true); - assertThatIllegalArgumentException().isThrownBy(this.interceptor::afterPropertiesSet); - } - - @Test - public void testEnsuresRunAsManagerSupportsFilterInvocationClass() throws Exception { - given(this.adm.supports(FilterInvocation.class)).willReturn(false); - assertThatIllegalArgumentException().isThrownBy(this.interceptor::afterPropertiesSet); - } - - /** - * We just test invocation works in a success event. There is no need to test access - * denied events as the abstract parent enforces that logic, which is extensively - * tested separately. - */ - @Test - public void testSuccessfulInvocation() throws Throwable { - // Setup a Context - Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED"); - SecurityContextHolder.getContext().setAuthentication(token); - FilterInvocation fi = createinvocation(); - given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK")); - this.interceptor.invoke(fi); - // SEC-1697 - verify(this.publisher, never()).publishEvent(any(AuthorizedEvent.class)); - } - - @Test - public void afterInvocationIsNotInvokedIfExceptionThrown() throws Exception { - Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED"); - SecurityContextHolder.getContext().setAuthentication(token); - FilterInvocation fi = createinvocation(); - FilterChain chain = fi.getChain(); - willThrow(new RuntimeException()).given(chain) - .doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK")); - AfterInvocationManager aim = mock(AfterInvocationManager.class); - this.interceptor.setAfterInvocationManager(aim); - assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> this.interceptor.invoke(fi)); - verifyNoMoreInteractions(aim); - } - - // SEC-1967 - @Test - @SuppressWarnings("unchecked") - public void finallyInvocationIsInvokedIfExceptionThrown() throws Exception { - SecurityContext ctx = SecurityContextHolder.getContext(); - Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED"); - token.setAuthenticated(true); - ctx.setAuthentication(token); - RunAsManager runAsManager = mock(RunAsManager.class); - given(runAsManager.buildRunAs(eq(token), any(), anyCollection())) - .willReturn(new RunAsUserToken("key", "someone", "creds", token.getAuthorities(), token.getClass())); - this.interceptor.setRunAsManager(runAsManager); - FilterInvocation fi = createinvocation(); - FilterChain chain = fi.getChain(); - willThrow(new RuntimeException()).given(chain) - .doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); - given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK")); - AfterInvocationManager aim = mock(AfterInvocationManager.class); - this.interceptor.setAfterInvocationManager(aim); - assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> this.interceptor.invoke(fi)); - // Check we've changed back - assertThat(SecurityContextHolder.getContext()).isSameAs(ctx); - assertThat(SecurityContextHolder.getContext().getAuthentication()).isSameAs(token); - } - - @Test - // gh-4997 - public void doFilterWhenObserveOncePerRequestThenAttributeNotSet() throws Exception { - this.interceptor.setObserveOncePerRequest(false); - MockHttpServletResponse response = new MockHttpServletResponse(); - MockHttpServletRequest request = new MockHttpServletRequest(); - this.interceptor.doFilter(request, response, new MockFilterChain()); - assertThat(request.getAttributeNames().hasMoreElements()).isFalse(); - } - - @Test - public void doFilterWhenObserveOncePerRequestFalseAndInvokedTwiceThenObserveTwice() throws Throwable { - Authentication token = new TestingAuthenticationToken("Test", "Password", "NOT_USED"); - SecurityContextHolder.getContext().setAuthentication(token); - FilterInvocation fi = createinvocation(); - given(this.ods.getAttributes(fi)).willReturn(SecurityConfig.createList("MOCK_OK")); - this.interceptor.invoke(fi); - this.interceptor.invoke(fi); - verify(this.adm, times(2)).decide(any(), any(), any()); - } - - private FilterInvocation createinvocation() { - MockHttpServletResponse response = new MockHttpServletResponse(); - MockHttpServletRequest request = TestMockHttpServletRequests.get("/secure/page.html").build(); - FilterChain chain = mock(FilterChain.class); - FilterInvocation fi = new FilterInvocation(request, response, chain); - return fi; - } - -} diff --git a/acl/spring-security-acl.gradle b/acl/spring-security-acl.gradle deleted file mode 100644 index ec3d59bbe78..00000000000 --- a/acl/spring-security-acl.gradle +++ /dev/null @@ -1,24 +0,0 @@ -apply plugin: 'io.spring.convention.spring-module' - -dependencies { - management platform(project(":spring-security-dependencies")) - api project(':spring-security-core') - api 'org.springframework:spring-aop' - api 'org.springframework:spring-context' - api 'org.springframework:spring-core' - api 'org.springframework:spring-jdbc' - api 'org.springframework:spring-tx' - - testImplementation "org.assertj:assertj-core" - testImplementation "org.junit.jupiter:junit-jupiter-api" - testImplementation "org.junit.jupiter:junit-jupiter-params" - testImplementation "org.junit.jupiter:junit-jupiter-engine" - testImplementation "org.mockito:mockito-core" - testImplementation "org.mockito:mockito-junit-jupiter" - testImplementation 'org.springframework:spring-beans' - testImplementation 'org.springframework:spring-context-support' - testImplementation "org.springframework:spring-test" - - testRuntimeOnly 'org.hsqldb:hsqldb' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' -} diff --git a/acl/src/main/java/org/springframework/security/acls/AclPermissionCacheOptimizer.java b/acl/src/main/java/org/springframework/security/acls/AclPermissionCacheOptimizer.java deleted file mode 100644 index 1274a2cf80c..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/AclPermissionCacheOptimizer.java +++ /dev/null @@ -1,82 +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; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.access.PermissionCacheOptimizer; -import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl; -import org.springframework.security.acls.domain.SidRetrievalStrategyImpl; -import org.springframework.security.acls.model.AclService; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; -import org.springframework.security.acls.model.Sid; -import org.springframework.security.acls.model.SidRetrievalStrategy; -import org.springframework.security.core.Authentication; - -/** - * Batch loads ACLs for collections of objects to allow optimised filtering. - * - * @author Luke Taylor - * @since 3.1 - */ -public class AclPermissionCacheOptimizer implements PermissionCacheOptimizer { - - private final Log logger = LogFactory.getLog(getClass()); - - private final AclService aclService; - - private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl(); - - private ObjectIdentityRetrievalStrategy oidRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl(); - - public AclPermissionCacheOptimizer(AclService aclService) { - this.aclService = aclService; - } - - @Override - public void cachePermissionsFor(Authentication authentication, Collection objects) { - if (objects.isEmpty()) { - return; - } - List oidsToCache = new ArrayList<>(objects.size()); - for (Object domainObject : objects) { - if (domainObject != null) { - ObjectIdentity oid = this.oidRetrievalStrategy.getObjectIdentity(domainObject); - oidsToCache.add(oid); - } - } - List sids = this.sidRetrievalStrategy.getSids(authentication); - this.logger.debug(LogMessage.of(() -> "Eagerly loading Acls for " + oidsToCache.size() + " objects")); - this.aclService.readAclsById(oidsToCache, sids); - } - - public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) { - this.oidRetrievalStrategy = objectIdentityRetrievalStrategy; - } - - public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) { - this.sidRetrievalStrategy = sidRetrievalStrategy; - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/AclPermissionEvaluator.java b/acl/src/main/java/org/springframework/security/acls/AclPermissionEvaluator.java deleted file mode 100644 index 9e60d9c9b9c..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/AclPermissionEvaluator.java +++ /dev/null @@ -1,155 +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; - -import java.io.Serializable; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.log.LogMessage; -import org.springframework.security.access.PermissionEvaluator; -import org.springframework.security.acls.domain.DefaultPermissionFactory; -import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl; -import org.springframework.security.acls.domain.PermissionFactory; -import org.springframework.security.acls.domain.SidRetrievalStrategyImpl; -import org.springframework.security.acls.model.Acl; -import org.springframework.security.acls.model.AclService; -import org.springframework.security.acls.model.NotFoundException; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.ObjectIdentityGenerator; -import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; -import org.springframework.security.acls.model.Permission; -import org.springframework.security.acls.model.Sid; -import org.springframework.security.acls.model.SidRetrievalStrategy; -import org.springframework.security.core.Authentication; - -/** - * 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 - * {@link org.springframework.security.acls.AclEntryVoter AclEntryVoter}. - * - * @author Luke Taylor - * @since 3.0 - */ -public class AclPermissionEvaluator implements PermissionEvaluator { - - private final Log logger = LogFactory.getLog(getClass()); - - private final AclService aclService; - - private ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy = new ObjectIdentityRetrievalStrategyImpl(); - - private ObjectIdentityGenerator objectIdentityGenerator = new ObjectIdentityRetrievalStrategyImpl(); - - private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl(); - - private PermissionFactory permissionFactory = new DefaultPermissionFactory(); - - public AclPermissionEvaluator(AclService aclService) { - this.aclService = aclService; - } - - /** - * Determines whether the user has the given permission(s) on the domain object using - * the ACL configuration. If the domain object is null, returns false (this can always - * be overridden using a null check in the expression itself). - */ - @Override - public boolean hasPermission(Authentication authentication, Object domainObject, Object permission) { - if (domainObject == null) { - return false; - } - ObjectIdentity objectIdentity = this.objectIdentityRetrievalStrategy.getObjectIdentity(domainObject); - return checkPermission(authentication, objectIdentity, permission); - } - - @Override - public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, - Object permission) { - ObjectIdentity objectIdentity = this.objectIdentityGenerator.createObjectIdentity(targetId, targetType); - return checkPermission(authentication, objectIdentity, permission); - } - - private boolean checkPermission(Authentication authentication, ObjectIdentity oid, Object permission) { - // Obtain the SIDs applicable to the principal - List sids = this.sidRetrievalStrategy.getSids(authentication); - List requiredPermission = resolvePermission(permission); - this.logger.debug(LogMessage.of(() -> "Checking permission '" + permission + "' for object '" + oid + "'")); - try { - // Lookup only ACLs for SIDs we're interested in - Acl acl = this.aclService.readAclById(oid, sids); - if (acl.isGranted(requiredPermission, sids, false)) { - this.logger.debug("Access is granted"); - return true; - } - this.logger.debug("Returning false - ACLs returned, but insufficient permissions for this principal"); - } - catch (NotFoundException nfe) { - this.logger.debug("Returning false - no ACLs apply for this principal"); - } - return false; - } - - List resolvePermission(Object permission) { - if (permission instanceof Integer) { - return Arrays.asList(this.permissionFactory.buildFromMask((Integer) permission)); - } - if (permission instanceof Permission) { - return Arrays.asList((Permission) permission); - } - if (permission instanceof Permission[]) { - return Arrays.asList((Permission[]) permission); - } - if (permission instanceof String permString) { - Permission p = buildPermission(permString); - if (p != null) { - return Arrays.asList(p); - } - } - throw new IllegalArgumentException("Unsupported permission: " + permission); - } - - private Permission buildPermission(String permString) { - try { - return this.permissionFactory.buildFromName(permString); - } - catch (IllegalArgumentException notfound) { - return this.permissionFactory.buildFromName(permString.toUpperCase(Locale.ENGLISH)); - } - } - - public void setObjectIdentityRetrievalStrategy(ObjectIdentityRetrievalStrategy objectIdentityRetrievalStrategy) { - this.objectIdentityRetrievalStrategy = objectIdentityRetrievalStrategy; - } - - public void setObjectIdentityGenerator(ObjectIdentityGenerator objectIdentityGenerator) { - this.objectIdentityGenerator = objectIdentityGenerator; - } - - public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) { - this.sidRetrievalStrategy = sidRetrievalStrategy; - } - - public void setPermissionFactory(PermissionFactory permissionFactory) { - this.permissionFactory = permissionFactory; - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/AbstractPermission.java b/acl/src/main/java/org/springframework/security/acls/domain/AbstractPermission.java deleted file mode 100644 index 9458aadc232..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/AbstractPermission.java +++ /dev/null @@ -1,85 +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.domain; - -import org.springframework.security.acls.model.Permission; - -/** - * Provides an abstract superclass for {@link Permission} implementations. - * - * @author Ben Alex - * @since 2.0.3 - */ -public abstract class AbstractPermission implements Permission { - - protected final char code; - - protected int mask; - - /** - * Sets the permission mask and uses the '*' character to represent active bits when - * represented as a bit pattern string. - * @param mask the integer bit mask for the permission - */ - protected AbstractPermission(int mask) { - this.mask = mask; - this.code = '*'; - } - - /** - * Sets the permission mask and uses the specified character for active bits. - * @param mask the integer bit mask for the permission - * @param code the character to print for each active bit in the mask (see - * {@link Permission#getPattern()}) - */ - protected AbstractPermission(int mask, char code) { - this.mask = mask; - this.code = code; - } - - @Override - public final boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (!(obj instanceof Permission other)) { - return false; - } - return (this.mask == other.getMask()); - } - - @Override - public final int hashCode() { - return this.mask; - } - - @Override - public final String toString() { - return this.getClass().getSimpleName() + "[" + getPattern() + "=" + this.mask + "]"; - } - - @Override - public final int getMask() { - return this.mask; - } - - @Override - public String getPattern() { - return AclFormattingUtils.printBinary(this.mask, this.code); - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/AccessControlEntryImpl.java b/acl/src/main/java/org/springframework/security/acls/domain/AccessControlEntryImpl.java deleted file mode 100644 index 0e8b013fc46..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/AccessControlEntryImpl.java +++ /dev/null @@ -1,192 +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.domain; - -import java.io.Serializable; - -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.Permission; -import org.springframework.security.acls.model.Sid; -import org.springframework.util.Assert; - -/** - * An immutable default implementation of AccessControlEntry. - * - * @author Ben Alex - */ -public class AccessControlEntryImpl implements AccessControlEntry, AuditableAccessControlEntry { - - private final Acl acl; - - private Permission permission; - - private final Serializable id; - - private final Sid sid; - - private boolean auditFailure = false; - - private boolean auditSuccess = false; - - private final boolean granting; - - public AccessControlEntryImpl(Serializable id, Acl acl, Sid sid, Permission permission, boolean granting, - boolean auditSuccess, boolean auditFailure) { - Assert.notNull(acl, "Acl required"); - Assert.notNull(sid, "Sid required"); - Assert.notNull(permission, "Permission required"); - this.id = id; - this.acl = acl; // can be null - this.sid = sid; - this.permission = permission; - this.granting = granting; - this.auditSuccess = auditSuccess; - this.auditFailure = auditFailure; - } - - @Override - public boolean equals(Object arg0) { - if (!(arg0 instanceof AccessControlEntryImpl)) { - return false; - } - AccessControlEntryImpl other = (AccessControlEntryImpl) arg0; - if (this.acl == null) { - if (other.getAcl() != null) { - return false; - } - // Both this.acl and rhs.acl are null and thus equal - } - else { - // this.acl is non-null - if (other.getAcl() == null) { - return false; - } - - // Both this.acl and rhs.acl are non-null, so do a comparison - if (this.acl.getObjectIdentity() == null) { - if (other.acl.getObjectIdentity() != null) { - return false; - } - // Both this.acl and rhs.acl are null and thus equal - } - else { - // Both this.acl.objectIdentity and rhs.acl.objectIdentity are non-null - if (!this.acl.getObjectIdentity().equals(other.getAcl().getObjectIdentity())) { - return false; - } - } - } - if (this.id == null) { - if (other.id != null) { - return false; - } - // Both this.id and rhs.id are null and thus equal - } - else { - // this.id is non-null - if (other.id == null) { - return false; - } - // Both this.id and rhs.id are non-null - if (!this.id.equals(other.id)) { - return false; - } - } - if ((this.auditFailure != other.isAuditFailure()) || (this.auditSuccess != other.isAuditSuccess()) - || (this.granting != other.isGranting()) || !this.permission.equals(other.getPermission()) - || !this.sid.equals(other.getSid())) { - return false; - } - return true; - } - - @Override - public int hashCode() { - int result = this.permission.hashCode(); - result = 31 * result + ((this.id != null) ? this.id.hashCode() : 0); - result = 31 * result + (this.sid.hashCode()); - result = 31 * result + (this.auditFailure ? 1 : 0); - result = 31 * result + (this.auditSuccess ? 1 : 0); - result = 31 * result + (this.granting ? 1 : 0); - return result; - } - - @Override - public Acl getAcl() { - return this.acl; - } - - @Override - public Serializable getId() { - return this.id; - } - - @Override - public Permission getPermission() { - return this.permission; - } - - @Override - public Sid getSid() { - return this.sid; - } - - @Override - public boolean isAuditFailure() { - return this.auditFailure; - } - - @Override - public boolean isAuditSuccess() { - return this.auditSuccess; - } - - @Override - public boolean isGranting() { - return this.granting; - } - - void setAuditFailure(boolean auditFailure) { - this.auditFailure = auditFailure; - } - - void setAuditSuccess(boolean auditSuccess) { - this.auditSuccess = auditSuccess; - } - - void setPermission(Permission permission) { - Assert.notNull(permission, "Permission required"); - this.permission = permission; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("AccessControlEntryImpl["); - sb.append("id: ").append(this.id).append("; "); - sb.append("granting: ").append(this.granting).append("; "); - sb.append("sid: ").append(this.sid).append("; "); - sb.append("permission: ").append(this.permission).append("; "); - sb.append("auditSuccess: ").append(this.auditSuccess).append("; "); - sb.append("auditFailure: ").append(this.auditFailure); - sb.append("]"); - return sb.toString(); - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategy.java b/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategy.java deleted file mode 100644 index fa243b0fcf7..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategy.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.domain; - -import org.springframework.security.acls.model.Acl; - -/** - * Strategy used by {@link AclImpl} to determine whether a principal is permitted to call - * adminstrative methods on the AclImpl. - * - * @author Ben Alex - */ -public interface AclAuthorizationStrategy { - - int CHANGE_OWNERSHIP = 0; - - int CHANGE_AUDITING = 1; - - int CHANGE_GENERAL = 2; - - void securityCheck(Acl acl, int changeType); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java b/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java deleted file mode 100644 index 07349531b43..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImpl.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.domain; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Set; - -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy; -import org.springframework.security.access.hierarchicalroles.RoleHierarchy; -import org.springframework.security.acls.model.Acl; -import org.springframework.security.acls.model.Sid; -import org.springframework.security.acls.model.SidRetrievalStrategy; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.util.Assert; - -/** - * Default implementation of {@link AclAuthorizationStrategy}. - *

- * Permission will be granted if at least one of the following conditions is true for the - * current principal. - *

    - *
  • is the owner (as defined by the ACL).
  • - *
  • holds the relevant system-wide {@link GrantedAuthority} injected into the - * constructor.
  • - *
  • has {@link BasePermission#ADMINISTRATION} permission (as defined by the ACL).
  • - *
- * - * @author Ben Alex - */ -public class AclAuthorizationStrategyImpl implements AclAuthorizationStrategy { - - private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder - .getContextHolderStrategy(); - - private final GrantedAuthority gaGeneralChanges; - - private final GrantedAuthority gaModifyAuditing; - - private final GrantedAuthority gaTakeOwnership; - - private SidRetrievalStrategy sidRetrievalStrategy = new SidRetrievalStrategyImpl(); - - private RoleHierarchy roleHierarchy = new NullRoleHierarchy(); - - /** - * Constructor. The only mandatory parameter relates to the system-wide - * {@link GrantedAuthority} instances that can be held to always permit ACL changes. - * @param auths the GrantedAuthoritys that have special permissions - * (index 0 is the authority needed to change ownership, index 1 is the authority - * needed to modify auditing details, index 2 is the authority needed to change other - * ACL and ACE details) (required) - *

- * Alternatively, a single value can be supplied for all three permissions. - */ - public AclAuthorizationStrategyImpl(GrantedAuthority... auths) { - Assert.isTrue(auths != null && (auths.length == 3 || auths.length == 1), - "One or three GrantedAuthority instances required"); - if (auths.length == 3) { - this.gaTakeOwnership = auths[0]; - this.gaModifyAuditing = auths[1]; - this.gaGeneralChanges = auths[2]; - } - else { - this.gaTakeOwnership = auths[0]; - this.gaModifyAuditing = auths[0]; - this.gaGeneralChanges = auths[0]; - } - } - - @Override - public void securityCheck(Acl acl, int changeType) { - SecurityContext context = this.securityContextHolderStrategy.getContext(); - if ((context == null) || (context.getAuthentication() == null) - || !context.getAuthentication().isAuthenticated()) { - throw new AccessDeniedException("Authenticated principal required to operate with ACLs"); - } - Authentication authentication = context.getAuthentication(); - // Check if authorized by virtue of ACL ownership - Sid currentUser = createCurrentUser(authentication); - if (currentUser.equals(acl.getOwner()) - && ((changeType == CHANGE_GENERAL) || (changeType == CHANGE_OWNERSHIP))) { - return; - } - - // Iterate this principal's authorities to determine right - Collection reachableGrantedAuthorities = this.roleHierarchy - .getReachableGrantedAuthorities(authentication.getAuthorities()); - Set authorities = AuthorityUtils.authorityListToSet(reachableGrantedAuthorities); - if (acl.getOwner() instanceof GrantedAuthoritySid - && authorities.contains(((GrantedAuthoritySid) acl.getOwner()).getGrantedAuthority())) { - return; - } - - // Not authorized by ACL ownership; try via adminstrative permissions - GrantedAuthority requiredAuthority = getRequiredAuthority(changeType); - - if (authorities.contains(requiredAuthority.getAuthority())) { - return; - } - - // Try to get permission via ACEs within the ACL - List sids = this.sidRetrievalStrategy.getSids(authentication); - if (acl.isGranted(Arrays.asList(BasePermission.ADMINISTRATION), sids, false)) { - return; - } - - throw new AccessDeniedException( - "Principal does not have required ACL permissions to perform requested operation"); - } - - private GrantedAuthority getRequiredAuthority(int changeType) { - if (changeType == CHANGE_AUDITING) { - return this.gaModifyAuditing; - } - if (changeType == CHANGE_GENERAL) { - return this.gaGeneralChanges; - } - if (changeType == CHANGE_OWNERSHIP) { - return this.gaTakeOwnership; - } - throw new IllegalArgumentException("Unknown change type"); - } - - /** - * Creates a principal-like sid from the authentication information. - * @param authentication the authentication information that can provide principal and - * thus the sid's id will be dependant on the value inside - * @return a sid with the ID taken from the authentication information - */ - protected Sid createCurrentUser(Authentication authentication) { - return new PrincipalSid(authentication); - } - - public void setSidRetrievalStrategy(SidRetrievalStrategy sidRetrievalStrategy) { - Assert.notNull(sidRetrievalStrategy, "SidRetrievalStrategy required"); - this.sidRetrievalStrategy = sidRetrievalStrategy; - } - - /** - * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use - * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. - * - * @since 5.8 - */ - public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { - Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); - this.securityContextHolderStrategy = securityContextHolderStrategy; - } - - /** - * Sets the {@link RoleHierarchy} to use. The default is to use a - * {@link NullRoleHierarchy} - * @since 6.4 - */ - public void setRoleHierarchy(RoleHierarchy roleHierarchy) { - Assert.notNull(roleHierarchy, "roleHierarchy cannot be null"); - this.roleHierarchy = roleHierarchy; - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/AclFormattingUtils.java b/acl/src/main/java/org/springframework/security/acls/domain/AclFormattingUtils.java deleted file mode 100644 index 36bdb28a2da..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/AclFormattingUtils.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.domain; - -import org.springframework.security.acls.model.Permission; -import org.springframework.util.Assert; - -/** - * Utility methods for displaying ACL information. - * - * @author Ben Alex - */ -public abstract class AclFormattingUtils { - - public static String demergePatterns(String original, String removeBits) { - Assert.notNull(original, "Original string required"); - Assert.notNull(removeBits, "Bits To Remove string required"); - Assert.isTrue(original.length() == removeBits.length(), - "Original and Bits To Remove strings must be identical length"); - char[] replacement = new char[original.length()]; - for (int i = 0; i < original.length(); i++) { - if (removeBits.charAt(i) == Permission.RESERVED_OFF) { - replacement[i] = original.charAt(i); - } - else { - replacement[i] = Permission.RESERVED_OFF; - } - } - return new String(replacement); - } - - public static String mergePatterns(String original, String extraBits) { - Assert.notNull(original, "Original string required"); - Assert.notNull(extraBits, "Extra Bits string required"); - Assert.isTrue(original.length() == extraBits.length(), - "Original and Extra Bits strings must be identical length"); - char[] replacement = new char[extraBits.length()]; - for (int i = 0; i < extraBits.length(); i++) { - if (extraBits.charAt(i) == Permission.RESERVED_OFF) { - replacement[i] = original.charAt(i); - } - else { - replacement[i] = extraBits.charAt(i); - } - } - return new String(replacement); - } - - /** - * Returns a representation of the active bits in the presented mask, with each active - * bit being denoted by character '*'. - *

- * Inactive bits will be denoted by character {@link Permission#RESERVED_OFF}. - * @param i the integer bit mask to print the active bits for - * @return a 32-character representation of the bit mask - */ - public static String printBinary(int i) { - return printBinary(i, '*', Permission.RESERVED_OFF); - } - - /** - * Returns a representation of the active bits in the presented mask, with each active - * bit being denoted by the passed character. - *

- * Inactive bits will be denoted by character {@link Permission#RESERVED_OFF}. - * @param mask the integer bit mask to print the active bits for - * @param code the character to print when an active bit is detected - * @return a 32-character representation of the bit mask - */ - public static String printBinary(int mask, char code) { - Assert.doesNotContain(Character.toString(code), Character.toString(Permission.RESERVED_ON), - () -> Permission.RESERVED_ON + " is a reserved character code"); - Assert.doesNotContain(Character.toString(code), Character.toString(Permission.RESERVED_OFF), - () -> Permission.RESERVED_OFF + " is a reserved character code"); - return printBinary(mask, Permission.RESERVED_ON, Permission.RESERVED_OFF).replace(Permission.RESERVED_ON, code); - } - - private static String printBinary(int i, char on, char off) { - String s = Integer.toBinaryString(i); - String pattern = Permission.THIRTY_TWO_RESERVED_OFF; - String temp2 = pattern.substring(0, pattern.length() - s.length()) + s; - return temp2.replace('0', off).replace('1', on); - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/AclImpl.java b/acl/src/main/java/org/springframework/security/acls/domain/AclImpl.java deleted file mode 100644 index 8550450e8f2..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/AclImpl.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.domain; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -import org.springframework.security.acls.model.AccessControlEntry; -import org.springframework.security.acls.model.Acl; -import org.springframework.security.acls.model.AuditableAcl; -import org.springframework.security.acls.model.MutableAcl; -import org.springframework.security.acls.model.NotFoundException; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.OwnershipAcl; -import org.springframework.security.acls.model.Permission; -import org.springframework.security.acls.model.PermissionGrantingStrategy; -import org.springframework.security.acls.model.Sid; -import org.springframework.security.acls.model.UnloadedSidException; -import org.springframework.util.Assert; -import org.springframework.util.ObjectUtils; - -/** - * Base implementation of Acl. - * - * @author Ben Alex - */ -public class AclImpl implements Acl, MutableAcl, AuditableAcl, OwnershipAcl { - - private Acl parentAcl; - - private transient AclAuthorizationStrategy aclAuthorizationStrategy; - - private transient PermissionGrantingStrategy permissionGrantingStrategy; - - private final List aces = new ArrayList<>(); - - private ObjectIdentity objectIdentity; - - private Serializable id; - - // OwnershipAcl - private Sid owner; - - // includes all SIDs the WHERE clause covered, even if there was no ACE for a SID - private List loadedSids = null; - - private boolean entriesInheriting = true; - - /** - * Minimal constructor, which should be used - * {@link org.springframework.security.acls.model.MutableAclService#createAcl(ObjectIdentity)} - * . - * @param objectIdentity the object identity this ACL relates to (required) - * @param id the primary key assigned to this ACL (required) - * @param aclAuthorizationStrategy authorization strategy (required) - * @param auditLogger audit logger (required) - */ - public AclImpl(ObjectIdentity objectIdentity, Serializable id, AclAuthorizationStrategy aclAuthorizationStrategy, - AuditLogger auditLogger) { - Assert.notNull(objectIdentity, "Object Identity required"); - Assert.notNull(id, "Id required"); - Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required"); - Assert.notNull(auditLogger, "AuditLogger required"); - this.objectIdentity = objectIdentity; - this.id = id; - this.aclAuthorizationStrategy = aclAuthorizationStrategy; - this.permissionGrantingStrategy = new DefaultPermissionGrantingStrategy(auditLogger); - } - - /** - * Full constructor, which should be used by persistence tools that do not provide - * field-level access features. - * @param objectIdentity the object identity this ACL relates to - * @param id the primary key assigned to this ACL - * @param aclAuthorizationStrategy authorization strategy - * @param grantingStrategy the {@code PermissionGrantingStrategy} which will be used - * by the {@code isGranted()} method - * @param parentAcl the parent (may be may be {@code null}) - * @param loadedSids the loaded SIDs if only a subset were loaded (may be {@code null} - * ) - * @param entriesInheriting if ACEs from the parent should inherit into this ACL - * @param owner the owner (required) - */ - public AclImpl(ObjectIdentity objectIdentity, Serializable id, AclAuthorizationStrategy aclAuthorizationStrategy, - PermissionGrantingStrategy grantingStrategy, Acl parentAcl, List loadedSids, boolean entriesInheriting, - Sid owner) { - Assert.notNull(objectIdentity, "Object Identity required"); - Assert.notNull(id, "Id required"); - Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required"); - Assert.notNull(owner, "Owner required"); - this.objectIdentity = objectIdentity; - this.id = id; - this.aclAuthorizationStrategy = aclAuthorizationStrategy; - this.parentAcl = parentAcl; // may be null - this.loadedSids = loadedSids; // may be null - this.entriesInheriting = entriesInheriting; - this.owner = owner; - this.permissionGrantingStrategy = grantingStrategy; - } - - /** - * Private no-argument constructor for use by reflection-based persistence tools along - * with field-level access. - */ - @SuppressWarnings("unused") - private AclImpl() { - } - - @Override - public void deleteAce(int aceIndex) throws NotFoundException { - this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL); - verifyAceIndexExists(aceIndex); - synchronized (this.aces) { - this.aces.remove(aceIndex); - } - } - - private void verifyAceIndexExists(int aceIndex) { - if (aceIndex < 0) { - throw new NotFoundException("aceIndex must be greater than or equal to zero"); - } - if (aceIndex >= this.aces.size()) { - throw new NotFoundException("aceIndex must refer to an index of the AccessControlEntry list. " - + "List size is " + this.aces.size() + ", index was " + aceIndex); - } - } - - @Override - public void insertAce(int atIndexLocation, Permission permission, Sid sid, boolean granting) - throws NotFoundException { - this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL); - Assert.notNull(permission, "Permission required"); - Assert.notNull(sid, "Sid required"); - if (atIndexLocation < 0) { - throw new NotFoundException("atIndexLocation must be greater than or equal to zero"); - } - if (atIndexLocation > this.aces.size()) { - throw new NotFoundException( - "atIndexLocation must be less than or equal to the size of the AccessControlEntry collection"); - } - AccessControlEntryImpl ace = new AccessControlEntryImpl(null, this, sid, permission, granting, false, false); - synchronized (this.aces) { - this.aces.add(atIndexLocation, ace); - } - } - - @Override - public List getEntries() { - // Can safely return AccessControlEntry directly, as they're immutable outside the - // ACL package - return new ArrayList<>(this.aces); - } - - @Override - public Serializable getId() { - return this.id; - } - - @Override - public ObjectIdentity getObjectIdentity() { - return this.objectIdentity; - } - - @Override - public boolean isEntriesInheriting() { - return this.entriesInheriting; - } - - /** - * Delegates to the {@link PermissionGrantingStrategy}. - * @throws UnloadedSidException if the passed SIDs are unknown to this ACL because the - * ACL was only loaded for a subset of SIDs - * @see DefaultPermissionGrantingStrategy - */ - @Override - public boolean isGranted(List permission, List sids, boolean administrativeMode) - throws NotFoundException, UnloadedSidException { - Assert.notEmpty(permission, "Permissions required"); - Assert.notEmpty(sids, "SIDs required"); - if (!this.isSidLoaded(sids)) { - throw new UnloadedSidException("ACL was not loaded for one or more SID"); - } - return this.permissionGrantingStrategy.isGranted(this, permission, sids, administrativeMode); - } - - @Override - public boolean isSidLoaded(List sids) { - // If loadedSides is null, this indicates all SIDs were loaded - // Also return true if the caller didn't specify a SID to find - if ((this.loadedSids == null) || (sids == null) || sids.isEmpty()) { - return true; - } - - // This ACL applies to a SID subset only. Iterate to check it applies. - for (Sid sid : sids) { - boolean found = false; - for (Sid loadedSid : this.loadedSids) { - if (sid.equals(loadedSid)) { - // this SID is OK - found = true; - break; // out of loadedSids for loop - } - } - if (!found) { - return false; - } - } - - return true; - } - - @Override - public void setEntriesInheriting(boolean entriesInheriting) { - this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL); - this.entriesInheriting = entriesInheriting; - } - - @Override - public void setOwner(Sid newOwner) { - this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_OWNERSHIP); - Assert.notNull(newOwner, "Owner required"); - this.owner = newOwner; - } - - @Override - public Sid getOwner() { - return this.owner; - } - - @Override - public void setParent(Acl newParent) { - this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL); - Assert.isTrue(newParent == null || !newParent.equals(this), "Cannot be the parent of yourself"); - this.parentAcl = newParent; - } - - @Override - public Acl getParentAcl() { - return this.parentAcl; - } - - @Override - public void updateAce(int aceIndex, Permission permission) throws NotFoundException { - this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_GENERAL); - verifyAceIndexExists(aceIndex); - synchronized (this.aces) { - AccessControlEntryImpl ace = (AccessControlEntryImpl) this.aces.get(aceIndex); - ace.setPermission(permission); - } - } - - @Override - public void updateAuditing(int aceIndex, boolean auditSuccess, boolean auditFailure) { - this.aclAuthorizationStrategy.securityCheck(this, AclAuthorizationStrategy.CHANGE_AUDITING); - verifyAceIndexExists(aceIndex); - synchronized (this.aces) { - AccessControlEntryImpl ace = (AccessControlEntryImpl) this.aces.get(aceIndex); - ace.setAuditSuccess(auditSuccess); - ace.setAuditFailure(auditFailure); - } - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj == null || !(obj instanceof AclImpl)) { - return false; - } - AclImpl other = (AclImpl) obj; - boolean result = true; - result = result && this.aces.equals(other.aces); - result = result && ObjectUtils.nullSafeEquals(this.parentAcl, other.parentAcl); - result = result && ObjectUtils.nullSafeEquals(this.objectIdentity, other.objectIdentity); - result = result && ObjectUtils.nullSafeEquals(this.id, other.id); - result = result && ObjectUtils.nullSafeEquals(this.owner, other.owner); - result = result && this.entriesInheriting == other.entriesInheriting; - result = result && ObjectUtils.nullSafeEquals(this.loadedSids, other.loadedSids); - return result; - } - - @Override - public int hashCode() { - int result = (this.parentAcl != null) ? this.parentAcl.hashCode() : 0; - result = 31 * result + this.aclAuthorizationStrategy.hashCode(); - result = 31 * result - + ((this.permissionGrantingStrategy != null) ? this.permissionGrantingStrategy.hashCode() : 0); - result = 31 * result + ((this.aces != null) ? this.aces.hashCode() : 0); - result = 31 * result + this.objectIdentity.hashCode(); - result = 31 * result + this.id.hashCode(); - result = 31 * result + ((this.owner != null) ? this.owner.hashCode() : 0); - result = 31 * result + ((this.loadedSids != null) ? this.loadedSids.hashCode() : 0); - result = 31 * result + (this.entriesInheriting ? 1 : 0); - return result; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("AclImpl["); - sb.append("id: ").append(this.id).append("; "); - sb.append("objectIdentity: ").append(this.objectIdentity).append("; "); - sb.append("owner: ").append(this.owner).append("; "); - int count = 0; - for (AccessControlEntry ace : this.aces) { - count++; - if (count == 1) { - sb.append("\n"); - } - sb.append(ace).append("\n"); - } - if (count == 0) { - sb.append("no ACEs; "); - } - sb.append("inheriting: ").append(this.entriesInheriting).append("; "); - sb.append("parent: ").append((this.parentAcl == null) ? "Null" : this.parentAcl.getObjectIdentity().toString()); - sb.append("; "); - sb.append("aclAuthorizationStrategy: ").append(this.aclAuthorizationStrategy).append("; "); - sb.append("permissionGrantingStrategy: ").append(this.permissionGrantingStrategy); - sb.append("]"); - return sb.toString(); - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/AuditLogger.java b/acl/src/main/java/org/springframework/security/acls/domain/AuditLogger.java deleted file mode 100644 index 20edb6aa23c..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/AuditLogger.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.domain; - -import org.springframework.security.acls.model.AccessControlEntry; - -/** - * Used by AclImpl to log audit events. - * - * @author Ben Alex - */ -public interface AuditLogger { - - void logIfNeeded(boolean granted, AccessControlEntry ace); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/BasePermission.java b/acl/src/main/java/org/springframework/security/acls/domain/BasePermission.java deleted file mode 100644 index 5da94f08d4e..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/BasePermission.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.domain; - -import org.springframework.security.acls.model.Permission; - -/** - * A set of standard permissions. - * - *

- * You may subclass this class to add additional permissions, or use this class as a guide - * for creating your own permission classes. - *

- * - * @author Ben Alex - */ -public class BasePermission extends AbstractPermission { - - public static final Permission READ = new BasePermission(1 << 0, 'R'); // 1 - - public static final Permission WRITE = new BasePermission(1 << 1, 'W'); // 2 - - public static final Permission CREATE = new BasePermission(1 << 2, 'C'); // 4 - - public static final Permission DELETE = new BasePermission(1 << 3, 'D'); // 8 - - public static final Permission ADMINISTRATION = new BasePermission(1 << 4, 'A'); // 16 - - protected BasePermission(int mask) { - super(mask); - } - - protected BasePermission(int mask, char code) { - super(mask, code); - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/ConsoleAuditLogger.java b/acl/src/main/java/org/springframework/security/acls/domain/ConsoleAuditLogger.java deleted file mode 100644 index 744ec34148f..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/ConsoleAuditLogger.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.domain; - -import org.springframework.security.acls.model.AccessControlEntry; -import org.springframework.security.acls.model.AuditableAccessControlEntry; -import org.springframework.util.Assert; - -/** - * A basic implementation of {@link AuditLogger}. - * - * @author Ben Alex - */ -public class ConsoleAuditLogger implements AuditLogger { - - @Override - public void logIfNeeded(boolean granted, AccessControlEntry ace) { - Assert.notNull(ace, "AccessControlEntry required"); - if (ace instanceof AuditableAccessControlEntry) { - AuditableAccessControlEntry auditableAce = (AuditableAccessControlEntry) ace; - if (granted && auditableAce.isAuditSuccess()) { - System.out.println("GRANTED due to ACE: " + ace); - } - else if (!granted && auditableAce.isAuditFailure()) { - System.out.println("DENIED due to ACE: " + ace); - } - } - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/CumulativePermission.java b/acl/src/main/java/org/springframework/security/acls/domain/CumulativePermission.java deleted file mode 100644 index 819ce4e5f31..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/CumulativePermission.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.domain; - -import org.springframework.security.acls.model.Permission; - -/** - * Represents a Permission that is constructed at runtime from other - * permissions. - * - *

- * Methods return this, in order to facilitate method chaining. - *

- * - * @author Ben Alex - */ -public class CumulativePermission extends AbstractPermission { - - private String pattern = THIRTY_TWO_RESERVED_OFF; - - public CumulativePermission() { - super(0, ' '); - } - - public CumulativePermission clear(Permission permission) { - this.mask &= ~permission.getMask(); - this.pattern = AclFormattingUtils.demergePatterns(this.pattern, permission.getPattern()); - return this; - } - - public CumulativePermission clear() { - this.mask = 0; - this.pattern = THIRTY_TWO_RESERVED_OFF; - return this; - } - - public CumulativePermission set(Permission permission) { - this.mask |= permission.getMask(); - this.pattern = AclFormattingUtils.mergePatterns(this.pattern, permission.getPattern()); - return this; - } - - @Override - public String getPattern() { - return this.pattern; - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/DefaultPermissionFactory.java b/acl/src/main/java/org/springframework/security/acls/domain/DefaultPermissionFactory.java deleted file mode 100644 index 486a2e7ed02..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/DefaultPermissionFactory.java +++ /dev/null @@ -1,153 +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.domain; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.springframework.security.acls.model.Permission; -import org.springframework.util.Assert; - -/** - * Default implementation of {@link PermissionFactory}. - *

- * Used as a strategy by classes which wish to map integer masks and permission names to - * Permission instances for use with the ACL implementation. - *

- * Maintains a registry of permission names and masks to Permission instances. - * - * @author Ben Alex - * @author Luke Taylor - * @since 2.0.3 - */ -public class DefaultPermissionFactory implements PermissionFactory { - - private final Map registeredPermissionsByInteger = new HashMap<>(); - - private final Map registeredPermissionsByName = new HashMap<>(); - - /** - * Registers the Permission fields from the BasePermission class. - */ - public DefaultPermissionFactory() { - registerPublicPermissions(BasePermission.class); - } - - /** - * Registers the Permission fields from the supplied class. - */ - public DefaultPermissionFactory(Class permissionClass) { - registerPublicPermissions(permissionClass); - } - - /** - * Registers a map of named Permission instances. - * @param namedPermissions the map of Permissions, keyed by name. - */ - public DefaultPermissionFactory(Map namedPermissions) { - for (String name : namedPermissions.keySet()) { - registerPermission(namedPermissions.get(name), name); - } - } - - /** - * Registers the public static fields of type {@link Permission} for a give class. - *

- * These permissions will be registered under the name of the field. See - * {@link BasePermission} for an example. - * @param clazz a {@link Permission} class with public static fields to register - */ - protected void registerPublicPermissions(Class clazz) { - Assert.notNull(clazz, "Class required"); - Field[] fields = clazz.getFields(); - for (Field field : fields) { - try { - Object fieldValue = field.get(null); - if (Permission.class.isAssignableFrom(fieldValue.getClass())) { - // Found a Permission static field - Permission perm = (Permission) fieldValue; - String permissionName = field.getName(); - registerPermission(perm, permissionName); - } - } - catch (Exception ex) { - } - } - } - - protected void registerPermission(Permission perm, String permissionName) { - Assert.notNull(perm, "Permission required"); - Assert.hasText(permissionName, "Permission name required"); - Integer mask = perm.getMask(); - - // Ensure no existing Permission uses this integer or code - Assert.isTrue(!this.registeredPermissionsByInteger.containsKey(mask), - () -> "An existing Permission already provides mask " + mask); - Assert.isTrue(!this.registeredPermissionsByName.containsKey(permissionName), - () -> "An existing Permission already provides name '" + permissionName + "'"); - - // Register the new Permission - this.registeredPermissionsByInteger.put(mask, perm); - this.registeredPermissionsByName.put(permissionName, perm); - } - - @Override - public Permission buildFromMask(int mask) { - if (this.registeredPermissionsByInteger.containsKey(mask)) { - // The requested mask has an exact match against a statically-defined - // Permission, so return it - return this.registeredPermissionsByInteger.get(mask); - } - - // To get this far, we have to use a CumulativePermission - CumulativePermission permission = new CumulativePermission(); - for (int i = 0; i < 32; i++) { - int permissionToCheck = 1 << i; - if ((mask & permissionToCheck) == permissionToCheck) { - Permission p = this.registeredPermissionsByInteger.get(permissionToCheck); - Assert.state(p != null, - () -> "Mask '" + permissionToCheck + "' does not have a corresponding static Permission"); - permission.set(p); - } - } - return permission; - } - - @Override - public Permission buildFromName(String name) { - Permission p = this.registeredPermissionsByName.get(name); - Assert.notNull(p, "Unknown permission '" + name + "'"); - return p; - } - - @Override - public List buildFromNames(List names) { - if ((names == null) || names.isEmpty()) { - return Collections.emptyList(); - } - List permissions = new ArrayList<>(names.size()); - for (String name : names) { - permissions.add(buildFromName(name)); - } - return permissions; - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/DefaultPermissionGrantingStrategy.java b/acl/src/main/java/org/springframework/security/acls/domain/DefaultPermissionGrantingStrategy.java deleted file mode 100644 index 6813bd3e776..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/DefaultPermissionGrantingStrategy.java +++ /dev/null @@ -1,151 +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.domain; - -import java.util.List; - -import org.springframework.security.acls.model.AccessControlEntry; -import org.springframework.security.acls.model.Acl; -import org.springframework.security.acls.model.NotFoundException; -import org.springframework.security.acls.model.Permission; -import org.springframework.security.acls.model.PermissionGrantingStrategy; -import org.springframework.security.acls.model.Sid; -import org.springframework.util.Assert; - -public class DefaultPermissionGrantingStrategy implements PermissionGrantingStrategy { - - private final transient AuditLogger auditLogger; - - /** - * Creates an instance with the logger which will be used to record granting and - * denial of requested permissions. - */ - public DefaultPermissionGrantingStrategy(AuditLogger auditLogger) { - Assert.notNull(auditLogger, "auditLogger cannot be null"); - this.auditLogger = auditLogger; - } - - /** - * Determines authorization. The order of the permission and - * sid arguments is extremely important! The method will iterate - * through each of the permissions in the order specified. For each - * iteration, all of the sids will be considered, again in the order they - * are presented. A search will then be performed for the first - * {@link AccessControlEntry} object that directly matches that - * permission:sid combination. When the first full match is - * found (ie an ACE that has the SID currently being searched for and the exact - * permission bit mask being search for), the grant or deny flag for that ACE will - * prevail. If the ACE specifies to grant access, the method will return - * true. If the ACE specifies to deny access, the loop will stop and the - * next permission iteration will be performed. If each permission - * indicates to deny access, the first deny ACE found will be considered the reason - * for the failure (as it was the first match found, and is therefore the one most - * logically requiring changes - although not always). If absolutely no matching ACE - * was found at all for any permission, the parent ACL will be tried (provided that - * there is a parent and {@link Acl#isEntriesInheriting()} is true. The - * parent ACL will also scan its parent and so on. If ultimately no matching ACE is - * found, a NotFoundException will be thrown and the caller will need to - * decide how to handle the permission check. Similarly, if any of the SID arguments - * presented to the method were not loaded by the ACL, - * UnloadedSidException will be thrown. - * @param permission the exact permissions to scan for (order is important) - * @param sids the exact SIDs to scan for (order is important) - * @param administrativeMode if true denotes the query is for - * administrative purposes and no auditing will be undertaken - * @return true if one of the permissions has been granted, - * false if one of the permissions has been specifically revoked - * @throws NotFoundException if an exact ACE for one of the permission bit masks and - * SID combination could not be found - */ - @Override - public boolean isGranted(Acl acl, List permission, List sids, boolean administrativeMode) - throws NotFoundException { - List aces = acl.getEntries(); - AccessControlEntry firstRejection = null; - for (Permission p : permission) { - for (Sid sid : sids) { - // Attempt to find exact match for this permission mask and SID - boolean scanNextSid = true; - for (AccessControlEntry ace : aces) { - if (isGranted(ace, p) && ace.getSid().equals(sid)) { - // Found a matching ACE, so its authorization decision will - // prevail - if (ace.isGranting()) { - // Success - if (!administrativeMode) { - this.auditLogger.logIfNeeded(true, ace); - } - return true; - } - - // Failure for this permission, so stop search - // We will see if they have a different permission - // (this permission is 100% rejected for this SID) - if (firstRejection == null) { - // Store first rejection for auditing reasons - firstRejection = ace; - } - scanNextSid = false; // helps break the loop - - break; // exit aces loop - } - } - if (!scanNextSid) { - break; // exit SID for loop (now try next permission) - } - } - } - - if (firstRejection != null) { - // We found an ACE to reject the request at this point, as no - // other ACEs were found that granted a different permission - if (!administrativeMode) { - this.auditLogger.logIfNeeded(false, firstRejection); - } - return false; - } - - // No matches have been found so far - if (acl.isEntriesInheriting() && (acl.getParentAcl() != null)) { - // We have a parent, so let them try to find a matching ACE - return acl.getParentAcl().isGranted(permission, sids, false); - } - - // We either have no parent, or we're the uppermost parent - throw new NotFoundException("Unable to locate a matching ACE for passed permissions and SIDs"); - } - - /** - * Compares an ACE Permission to the given Permission. By default, we compare the - * Permission masks for exact match. Subclasses of this strategy can override this - * behavior and implement more sophisticated comparisons, e.g. a bitwise comparison - * for ACEs that grant access.

{@code
-	 * if (ace.isGranting() && p.getMask() != 0) {
-	 *    return (ace.getPermission().getMask() & p.getMask()) != 0;
-	 * } else {
-	 *    return ace.getPermission().getMask() == p.getMask();
-	 * }
-	 * }
- * @param ace the ACE from the Acl holding the mask. - * @param p the Permission we are checking against. - * @return true, if the respective masks are considered to be equal. - */ - protected boolean isGranted(AccessControlEntry ace, Permission p) { - return ace.getPermission().getMask() == p.getMask(); - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/GrantedAuthoritySid.java b/acl/src/main/java/org/springframework/security/acls/domain/GrantedAuthoritySid.java deleted file mode 100644 index 73c1dc0366d..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/GrantedAuthoritySid.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.domain; - -import org.springframework.security.acls.model.Sid; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.util.Assert; - -/** - * Represents a GrantedAuthority as a Sid. - *

- * This is a basic implementation that simply uses the String-based principal - * for Sid comparison. More complex principal objects may wish to provide an - * alternative Sid implementation that uses some other identifier. - *

- * - * @author Ben Alex - */ -public class GrantedAuthoritySid implements Sid { - - private final String grantedAuthority; - - public GrantedAuthoritySid(String grantedAuthority) { - Assert.hasText(grantedAuthority, "GrantedAuthority required"); - this.grantedAuthority = grantedAuthority; - } - - public GrantedAuthoritySid(GrantedAuthority grantedAuthority) { - Assert.notNull(grantedAuthority, "GrantedAuthority required"); - Assert.notNull(grantedAuthority.getAuthority(), - "This Sid is only compatible with GrantedAuthoritys that provide a non-null getAuthority()"); - this.grantedAuthority = grantedAuthority.getAuthority(); - } - - @Override - public boolean equals(Object object) { - if ((object == null) || !(object instanceof GrantedAuthoritySid)) { - return false; - } - // Delegate to getGrantedAuthority() to perform actual comparison (both should be - // identical) - return ((GrantedAuthoritySid) object).getGrantedAuthority().equals(this.getGrantedAuthority()); - } - - @Override - public int hashCode() { - return this.getGrantedAuthority().hashCode(); - } - - public String getGrantedAuthority() { - return this.grantedAuthority; - } - - @Override - public String toString() { - return "GrantedAuthoritySid[" + this.grantedAuthority + "]"; - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/IdentityUnavailableException.java b/acl/src/main/java/org/springframework/security/acls/domain/IdentityUnavailableException.java deleted file mode 100644 index c18fc336067..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/IdentityUnavailableException.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.domain; - -/** - * Thrown if an ACL identity could not be extracted from an object. - * - * @author Ben Alex - */ -public class IdentityUnavailableException extends RuntimeException { - - /** - * Constructs an IdentityUnavailableException with the specified message. - * @param msg the detail message - */ - public IdentityUnavailableException(String msg) { - super(msg); - } - - /** - * Constructs an IdentityUnavailableException with the specified message - * and root cause. - * @param msg the detail message - * @param cause root cause - */ - public IdentityUnavailableException(String msg, Throwable cause) { - super(msg, cause); - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/ObjectIdentityImpl.java b/acl/src/main/java/org/springframework/security/acls/domain/ObjectIdentityImpl.java deleted file mode 100644 index aafa3fff3f2..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/ObjectIdentityImpl.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.domain; - -import java.io.Serializable; -import java.lang.reflect.Method; - -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; - -/** - * Simple implementation of {@link ObjectIdentity}. - *

- * Uses Strings to store the identity of the domain object instance. Also - * offers a constructor that uses reflection to build the identity information. - * - * @author Ben Alex - */ -public class ObjectIdentityImpl implements ObjectIdentity { - - private final String type; - - private Serializable identifier; - - public ObjectIdentityImpl(String type, Serializable identifier) { - Assert.hasText(type, "Type required"); - Assert.notNull(identifier, "identifier required"); - this.identifier = identifier; - this.type = type; - } - - /** - * Constructor which uses the name of the supplied class as the type - * property. - */ - public ObjectIdentityImpl(Class javaType, Serializable identifier) { - Assert.notNull(javaType, "Java Type required"); - Assert.notNull(identifier, "identifier required"); - this.type = javaType.getName(); - this.identifier = identifier; - } - - /** - * Creates the ObjectIdentityImpl based on the passed object instance. - * The passed object must provide a getId() method, otherwise an - * exception will be thrown. - *

- * The class name of the object passed will be considered the {@link #type}, so if - * more control is required, a different constructor should be used. - * @param object the domain object instance to create an identity for. - * @throws IdentityUnavailableException if identity could not be extracted - */ - public ObjectIdentityImpl(Object object) throws IdentityUnavailableException { - Assert.notNull(object, "object cannot be null"); - Class typeClass = ClassUtils.getUserClass(object.getClass()); - this.type = typeClass.getName(); - Object result = invokeGetIdMethod(object, typeClass); - Assert.notNull(result, "getId() is required to return a non-null value"); - Assert.isInstanceOf(Serializable.class, result, "Getter must provide a return value of type Serializable"); - this.identifier = (Serializable) result; - } - - private Object invokeGetIdMethod(Object object, Class typeClass) { - try { - Method method = typeClass.getMethod("getId", new Class[] {}); - return method.invoke(object); - } - catch (Exception ex) { - throw new IdentityUnavailableException("Could not extract identity from object " + object, ex); - } - } - - /** - * Important so caching operates properly. - *

- * Considers an object of the same class equal if it has the same - * classname and id properties. - *

- * Numeric identities (Integer and Long values) are considered equal if they are - * numerically equal. Other serializable types are evaluated using a simple equality. - * @param obj object to compare - * @return true if the presented object matches this object - */ - @Override - public boolean equals(Object obj) { - if (obj == null || !(obj instanceof ObjectIdentityImpl)) { - return false; - } - ObjectIdentityImpl other = (ObjectIdentityImpl) obj; - if (this.identifier instanceof Number && other.identifier instanceof Number) { - // Integers and Longs with same value should be considered equal - if (((Number) this.identifier).longValue() != ((Number) other.identifier).longValue()) { - return false; - } - } - else { - // Use plain equality for other serializable types - if (!this.identifier.equals(other.identifier)) { - return false; - } - } - return this.type.equals(other.type); - } - - @Override - public Serializable getIdentifier() { - return this.identifier; - } - - @Override - public String getType() { - return this.type; - } - - /** - * Important so caching operates properly. - * @return the hash - */ - @Override - public int hashCode() { - int result = this.type.hashCode(); - result = 31 * result + this.identifier.hashCode(); - return result; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(this.getClass().getName()).append("["); - sb.append("Type: ").append(this.type); - sb.append("; Identifier: ").append(this.identifier).append("]"); - return sb.toString(); - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/ObjectIdentityRetrievalStrategyImpl.java b/acl/src/main/java/org/springframework/security/acls/domain/ObjectIdentityRetrievalStrategyImpl.java deleted file mode 100644 index d08ba91d8c4..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/ObjectIdentityRetrievalStrategyImpl.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.domain; - -import java.io.Serializable; - -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.ObjectIdentityGenerator; -import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; - -/** - * Basic implementation of {@link ObjectIdentityRetrievalStrategy} and - * ObjectIdentityGenerator that uses the constructors of - * {@link ObjectIdentityImpl} to create the {@link ObjectIdentity}. - * - * @author Ben Alex - */ -public class ObjectIdentityRetrievalStrategyImpl implements ObjectIdentityRetrievalStrategy, ObjectIdentityGenerator { - - @Override - public ObjectIdentity getObjectIdentity(Object domainObject) { - return new ObjectIdentityImpl(domainObject); - } - - @Override - public ObjectIdentity createObjectIdentity(Serializable id, String type) { - return new ObjectIdentityImpl(type, id); - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/PermissionFactory.java b/acl/src/main/java/org/springframework/security/acls/domain/PermissionFactory.java deleted file mode 100644 index 2511891a8c9..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/PermissionFactory.java +++ /dev/null @@ -1,44 +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.domain; - -import java.util.List; - -import org.springframework.security.acls.model.Permission; - -/** - * Provides a simple mechanism to retrieve {@link Permission} instances from integer - * masks. - * - * @author Ben Alex - * @since 2.0.3 - */ -public interface PermissionFactory { - - /** - * Dynamically creates a CumulativePermission or - * BasePermission representing the active bits in the passed mask. - * @param mask to build - * @return a Permission representing the requested object - */ - Permission buildFromMask(int mask); - - Permission buildFromName(String name); - - List buildFromNames(List names); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/PrincipalSid.java b/acl/src/main/java/org/springframework/security/acls/domain/PrincipalSid.java deleted file mode 100644 index 373d85a5e91..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/PrincipalSid.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.domain; - -import org.springframework.security.acls.model.Sid; -import org.springframework.security.core.Authentication; -import org.springframework.util.Assert; - -/** - * Represents an Authentication.getPrincipal() as a Sid. - *

- * This is a basic implementation that simply uses the String-based principal - * for Sid comparison. More complex principal objects may wish to provide an - * alternative Sid implementation that uses some other identifier. - *

- * - * @author Ben Alex - */ -public class PrincipalSid implements Sid { - - private final String principal; - - public PrincipalSid(String principal) { - Assert.hasText(principal, "Principal required"); - this.principal = principal; - } - - public PrincipalSid(Authentication authentication) { - Assert.notNull(authentication, "Authentication required"); - Assert.notNull(authentication.getPrincipal(), "Principal required"); - this.principal = authentication.getName(); - } - - @Override - public boolean equals(Object object) { - if ((object == null) || !(object instanceof PrincipalSid)) { - return false; - } - // Delegate to getPrincipal() to perform actual comparison (both should be - // identical) - return ((PrincipalSid) object).getPrincipal().equals(this.getPrincipal()); - } - - @Override - public int hashCode() { - return this.getPrincipal().hashCode(); - } - - public String getPrincipal() { - return this.principal; - } - - @Override - public String toString() { - return "PrincipalSid[" + this.principal + "]"; - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/SidRetrievalStrategyImpl.java b/acl/src/main/java/org/springframework/security/acls/domain/SidRetrievalStrategyImpl.java deleted file mode 100644 index d6f47ecd647..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/SidRetrievalStrategyImpl.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.domain; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy; -import org.springframework.security.access.hierarchicalroles.RoleHierarchy; -import org.springframework.security.acls.model.Sid; -import org.springframework.security.acls.model.SidRetrievalStrategy; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.util.Assert; - -/** - * Basic implementation of {@link SidRetrievalStrategy} that creates a {@link Sid} for the - * principal, as well as every granted authority the principal holds. Can optionally have - * a RoleHierarchy injected in order to determine the extended list of - * authorities that the principal is assigned. - *

- * The returned array will always contain the {@link PrincipalSid} before any - * {@link GrantedAuthoritySid} elements. - * - * @author Ben Alex - */ -public class SidRetrievalStrategyImpl implements SidRetrievalStrategy { - - private RoleHierarchy roleHierarchy = new NullRoleHierarchy(); - - public SidRetrievalStrategyImpl() { - } - - public SidRetrievalStrategyImpl(RoleHierarchy roleHierarchy) { - Assert.notNull(roleHierarchy, "RoleHierarchy must not be null"); - this.roleHierarchy = roleHierarchy; - } - - @Override - public List getSids(Authentication authentication) { - Collection authorities = this.roleHierarchy - .getReachableGrantedAuthorities(authentication.getAuthorities()); - List sids = new ArrayList<>(authorities.size() + 1); - sids.add(new PrincipalSid(authentication)); - for (GrantedAuthority authority : authorities) { - sids.add(new GrantedAuthoritySid(authority)); - } - return sids; - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/SpringCacheBasedAclCache.java b/acl/src/main/java/org/springframework/security/acls/domain/SpringCacheBasedAclCache.java deleted file mode 100644 index ab173f68e7f..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/SpringCacheBasedAclCache.java +++ /dev/null @@ -1,128 +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.domain; - -import java.io.Serializable; - -import org.springframework.cache.Cache; -import org.springframework.security.acls.model.AclCache; -import org.springframework.security.acls.model.MutableAcl; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.PermissionGrantingStrategy; -import org.springframework.security.util.FieldUtils; -import org.springframework.util.Assert; - -/** - * Simple implementation of {@link org.springframework.security.acls.model.AclCache} that - * delegates to {@link Cache} implementation. - *

- * Designed to handle the transient fields in - * {@link org.springframework.security.acls.domain.AclImpl}. Note that this implementation - * assumes all {@link org.springframework.security.acls.domain.AclImpl} instances share - * the same {@link org.springframework.security.acls.model.PermissionGrantingStrategy} and - * {@link org.springframework.security.acls.domain.AclAuthorizationStrategy} instances. - * - * @author Marten Deinum - * @since 3.2 - */ -public class SpringCacheBasedAclCache implements AclCache { - - private final Cache cache; - - private PermissionGrantingStrategy permissionGrantingStrategy; - - private AclAuthorizationStrategy aclAuthorizationStrategy; - - public SpringCacheBasedAclCache(Cache cache, PermissionGrantingStrategy permissionGrantingStrategy, - AclAuthorizationStrategy aclAuthorizationStrategy) { - Assert.notNull(cache, "Cache required"); - Assert.notNull(permissionGrantingStrategy, "PermissionGrantingStrategy required"); - Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required"); - this.cache = cache; - this.permissionGrantingStrategy = permissionGrantingStrategy; - this.aclAuthorizationStrategy = aclAuthorizationStrategy; - } - - @Override - public void evictFromCache(Serializable pk) { - Assert.notNull(pk, "Primary key (identifier) required"); - MutableAcl acl = getFromCache(pk); - if (acl != null) { - this.cache.evict(acl.getId()); - this.cache.evict(acl.getObjectIdentity()); - } - } - - @Override - public void evictFromCache(ObjectIdentity objectIdentity) { - Assert.notNull(objectIdentity, "ObjectIdentity required"); - MutableAcl acl = getFromCache(objectIdentity); - if (acl != null) { - this.cache.evict(acl.getId()); - this.cache.evict(acl.getObjectIdentity()); - } - } - - @Override - public MutableAcl getFromCache(ObjectIdentity objectIdentity) { - Assert.notNull(objectIdentity, "ObjectIdentity required"); - return getFromCache((Object) objectIdentity); - } - - @Override - public MutableAcl getFromCache(Serializable pk) { - Assert.notNull(pk, "Primary key (identifier) required"); - return getFromCache((Object) pk); - } - - @Override - public void putInCache(MutableAcl acl) { - Assert.notNull(acl, "Acl required"); - Assert.notNull(acl.getObjectIdentity(), "ObjectIdentity required"); - Assert.notNull(acl.getId(), "ID required"); - if ((acl.getParentAcl() != null) && (acl.getParentAcl() instanceof MutableAcl)) { - putInCache((MutableAcl) acl.getParentAcl()); - } - this.cache.put(acl.getObjectIdentity(), acl); - this.cache.put(acl.getId(), acl); - } - - private MutableAcl getFromCache(Object key) { - Cache.ValueWrapper element = this.cache.get(key); - if (element == null) { - return null; - } - return initializeTransientFields((MutableAcl) element.get()); - } - - private MutableAcl initializeTransientFields(MutableAcl value) { - if (value instanceof AclImpl) { - FieldUtils.setProtectedFieldValue("aclAuthorizationStrategy", value, this.aclAuthorizationStrategy); - FieldUtils.setProtectedFieldValue("permissionGrantingStrategy", value, this.permissionGrantingStrategy); - } - if (value.getParentAcl() != null) { - initializeTransientFields((MutableAcl) value.getParentAcl()); - } - return value; - } - - @Override - public void clearCache() { - this.cache.clear(); - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/domain/package-info.java b/acl/src/main/java/org/springframework/security/acls/domain/package-info.java deleted file mode 100644 index 60248ec5876..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/domain/package-info.java +++ /dev/null @@ -1,20 +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. - */ - -/** - * Basic implementation of access control lists (ACLs) interfaces. - */ -package org.springframework.security.acls.domain; diff --git a/acl/src/main/java/org/springframework/security/acls/jdbc/AclClassIdUtils.java b/acl/src/main/java/org/springframework/security/acls/jdbc/AclClassIdUtils.java deleted file mode 100644 index 9a1f5e4da18..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/jdbc/AclClassIdUtils.java +++ /dev/null @@ -1,168 +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.jdbc; - -import java.io.Serializable; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.UUID; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.convert.ConversionFailedException; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.core.convert.converter.Converter; -import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.util.Assert; - -/** - * Utility class for helping convert database representations of - * {@link ObjectIdentity#getIdentifier()} into the correct Java type as specified by - * acl_class.class_id_type. - * - * @author paulwheeler - */ -class AclClassIdUtils { - - private static final String DEFAULT_CLASS_ID_TYPE_COLUMN_NAME = "class_id_type"; - - private static final Log log = LogFactory.getLog(AclClassIdUtils.class); - - private ConversionService conversionService; - - AclClassIdUtils() { - GenericConversionService genericConversionService = new GenericConversionService(); - genericConversionService.addConverter(String.class, Long.class, new StringToLongConverter()); - genericConversionService.addConverter(String.class, UUID.class, new StringToUUIDConverter()); - this.conversionService = genericConversionService; - } - - AclClassIdUtils(ConversionService conversionService) { - Assert.notNull(conversionService, "conversionService must not be null"); - this.conversionService = conversionService; - } - - /** - * Converts the raw type from the database into the right Java type. For most - * applications the 'raw type' will be Long, for some applications it could be String. - * @param identifier The identifier from the database - * @param resultSet Result set of the query - * @return The identifier in the appropriate target Java type. Typically Long or UUID. - * @throws SQLException - */ - Serializable identifierFrom(Serializable identifier, ResultSet resultSet) throws SQLException { - if (isString(identifier) && hasValidClassIdType(resultSet) - && canConvertFromStringTo(classIdTypeFrom(resultSet))) { - return convertFromStringTo((String) identifier, classIdTypeFrom(resultSet)); - } - // Assume it should be a Long type - return convertToLong(identifier); - } - - private boolean hasValidClassIdType(ResultSet resultSet) { - try { - return classIdTypeFrom(resultSet) != null; - } - catch (SQLException ex) { - log.debug("Unable to obtain the class id type", ex); - return false; - } - } - - private Class classIdTypeFrom(ResultSet resultSet) throws SQLException { - return classIdTypeFrom(resultSet.getString(DEFAULT_CLASS_ID_TYPE_COLUMN_NAME)); - } - - private Class classIdTypeFrom(String className) { - if (className == null) { - return null; - } - try { - return (Class) Class.forName(className); - } - catch (ClassNotFoundException ex) { - log.debug("Unable to find class id type on classpath", ex); - return null; - } - } - - private boolean canConvertFromStringTo(Class targetType) { - return this.conversionService.canConvert(String.class, targetType); - } - - private T convertFromStringTo(String identifier, Class targetType) { - return this.conversionService.convert(identifier, targetType); - } - - /** - * Converts to a {@link Long}, attempting to use the {@link ConversionService} if - * available. - * @param identifier The identifier - * @return Long version of the identifier - * @throws NumberFormatException if the string cannot be parsed to a long. - * @throws org.springframework.core.convert.ConversionException if a conversion - * exception occurred - * @throws IllegalArgumentException if targetType is null - */ - private Long convertToLong(Serializable identifier) { - if (this.conversionService.canConvert(identifier.getClass(), Long.class)) { - return this.conversionService.convert(identifier, Long.class); - } - return Long.valueOf(identifier.toString()); - } - - private boolean isString(Serializable object) { - return object.getClass().isAssignableFrom(String.class); - } - - void setConversionService(ConversionService conversionService) { - Assert.notNull(conversionService, "conversionService must not be null"); - this.conversionService = conversionService; - } - - private static class StringToLongConverter implements Converter { - - @Override - public Long convert(String identifierAsString) { - if (identifierAsString == null) { - throw new ConversionFailedException(TypeDescriptor.valueOf(String.class), - TypeDescriptor.valueOf(Long.class), null, null); - - } - return Long.parseLong(identifierAsString); - } - - } - - private static class StringToUUIDConverter implements Converter { - - @Override - public UUID convert(String identifierAsString) { - if (identifierAsString == null) { - throw new ConversionFailedException(TypeDescriptor.valueOf(String.class), - TypeDescriptor.valueOf(UUID.class), null, null); - - } - return UUID.fromString(identifierAsString); - } - - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java b/acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java deleted file mode 100644 index 6f5499d19ba..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/jdbc/BasicLookupStrategy.java +++ /dev/null @@ -1,679 +0,0 @@ -/* - * Copyright 2004, 2005, 2006, 2017 Acegi Technology Pty Limited - * - * 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.jdbc; - -import java.io.Serializable; -import java.lang.reflect.Field; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.sql.DataSource; - -import org.springframework.core.convert.ConversionException; -import org.springframework.core.convert.ConversionService; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.ResultSetExtractor; -import org.springframework.security.acls.domain.AccessControlEntryImpl; -import org.springframework.security.acls.domain.AclAuthorizationStrategy; -import org.springframework.security.acls.domain.AclImpl; -import org.springframework.security.acls.domain.AuditLogger; -import org.springframework.security.acls.domain.DefaultPermissionFactory; -import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy; -import org.springframework.security.acls.domain.GrantedAuthoritySid; -import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl; -import org.springframework.security.acls.domain.PermissionFactory; -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.AclCache; -import org.springframework.security.acls.model.MutableAcl; -import org.springframework.security.acls.model.NotFoundException; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.ObjectIdentityGenerator; -import org.springframework.security.acls.model.Permission; -import org.springframework.security.acls.model.PermissionGrantingStrategy; -import org.springframework.security.acls.model.Sid; -import org.springframework.security.acls.model.UnloadedSidException; -import org.springframework.security.util.FieldUtils; -import org.springframework.util.Assert; - -/** - * Performs lookups in a manner that is compatible with ANSI SQL. - *

- * NB: This implementation does attempt to provide reasonably optimised lookups - within - * the constraints of a normalised database and standard ANSI SQL features. If you are - * willing to sacrifice either of these constraints (e.g. use a particular database - * feature such as hierarchical queries or materialized views, or reduce normalisation) - * you are likely to achieve better performance. In such situations you will need to - * provide your own custom LookupStrategy. This class does not support - * subclassing, as it is likely to change in future releases and therefore subclassing is - * unsupported. - *

- * There are two SQL queries executed, one in the lookupPrimaryKeys method and - * one in lookupObjectIdentities. These are built from the same select and "order - * by" clause, using a different where clause in each case. In order to use custom schema - * or column names, each of these SQL clauses can be customized, but they must be - * consistent with each other and with the expected result set generated by the default - * values. - * - * @author Ben Alex - */ -public class BasicLookupStrategy implements LookupStrategy { - - private static final String DEFAULT_SELECT_CLAUSE_COLUMNS = "select acl_object_identity.object_id_identity, " - + "acl_entry.ace_order, " + "acl_object_identity.id as acl_id, " + "acl_object_identity.parent_object, " - + "acl_object_identity.entries_inheriting, " + "acl_entry.id as ace_id, " + "acl_entry.mask, " - + "acl_entry.granting, " + "acl_entry.audit_success, " + "acl_entry.audit_failure, " - + "acl_sid.principal as ace_principal, " + "acl_sid.sid as ace_sid, " - + "acli_sid.principal as acl_principal, " + "acli_sid.sid as acl_sid, " + "acl_class.class "; - - private static final String DEFAULT_SELECT_CLAUSE_ACL_CLASS_ID_TYPE_COLUMN = ", acl_class.class_id_type "; - - private static final String DEFAULT_SELECT_CLAUSE_FROM = "from acl_object_identity " - + "left join acl_sid acli_sid on acli_sid.id = acl_object_identity.owner_sid " - + "left join acl_class on acl_class.id = acl_object_identity.object_id_class " - + "left join acl_entry on acl_object_identity.id = acl_entry.acl_object_identity " - + "left join acl_sid on acl_entry.sid = acl_sid.id " + "where ( "; - - public static final String DEFAULT_SELECT_CLAUSE = DEFAULT_SELECT_CLAUSE_COLUMNS + DEFAULT_SELECT_CLAUSE_FROM; - - public static final String DEFAULT_ACL_CLASS_ID_SELECT_CLAUSE = DEFAULT_SELECT_CLAUSE_COLUMNS - + DEFAULT_SELECT_CLAUSE_ACL_CLASS_ID_TYPE_COLUMN + DEFAULT_SELECT_CLAUSE_FROM; - - private static final String DEFAULT_LOOKUP_KEYS_WHERE_CLAUSE = "(acl_object_identity.id = ?)"; - - private static final String DEFAULT_LOOKUP_IDENTITIES_WHERE_CLAUSE = "(acl_object_identity.object_id_identity = ? and acl_class.class = ?)"; - - public static final String DEFAULT_ORDER_BY_CLAUSE = ") order by acl_object_identity.object_id_identity" - + " asc, acl_entry.ace_order asc"; - - private final AclAuthorizationStrategy aclAuthorizationStrategy; - - private ObjectIdentityGenerator objectIdentityGenerator; - - private PermissionFactory permissionFactory = new DefaultPermissionFactory(); - - private final AclCache aclCache; - - private final PermissionGrantingStrategy grantingStrategy; - - private final JdbcTemplate jdbcTemplate; - - private int batchSize = 50; - - private final Field fieldAces = FieldUtils.getField(AclImpl.class, "aces"); - - private final Field fieldAcl = FieldUtils.getField(AccessControlEntryImpl.class, "acl"); - - // SQL Customization fields - private String selectClause = DEFAULT_SELECT_CLAUSE; - - private String lookupPrimaryKeysWhereClause = DEFAULT_LOOKUP_KEYS_WHERE_CLAUSE; - - private String lookupObjectIdentitiesWhereClause = DEFAULT_LOOKUP_IDENTITIES_WHERE_CLAUSE; - - private String orderByClause = DEFAULT_ORDER_BY_CLAUSE; - - private AclClassIdUtils aclClassIdUtils; - - /** - * Constructor accepting mandatory arguments - * @param dataSource to access the database - * @param aclCache the cache where fully-loaded elements can be stored - * @param aclAuthorizationStrategy authorization strategy (required) - */ - public BasicLookupStrategy(DataSource dataSource, AclCache aclCache, - AclAuthorizationStrategy aclAuthorizationStrategy, AuditLogger auditLogger) { - this(dataSource, aclCache, aclAuthorizationStrategy, new DefaultPermissionGrantingStrategy(auditLogger)); - } - - /** - * Creates a new instance - * @param dataSource to access the database - * @param aclCache the cache where fully-loaded elements can be stored - * @param aclAuthorizationStrategy authorization strategy (required) - * @param grantingStrategy the PermissionGrantingStrategy - */ - public BasicLookupStrategy(DataSource dataSource, AclCache aclCache, - AclAuthorizationStrategy aclAuthorizationStrategy, PermissionGrantingStrategy grantingStrategy) { - Assert.notNull(dataSource, "DataSource required"); - Assert.notNull(aclCache, "AclCache required"); - Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required"); - Assert.notNull(grantingStrategy, "grantingStrategy required"); - this.jdbcTemplate = new JdbcTemplate(dataSource); - this.aclCache = aclCache; - this.aclAuthorizationStrategy = aclAuthorizationStrategy; - this.grantingStrategy = grantingStrategy; - this.objectIdentityGenerator = new ObjectIdentityRetrievalStrategyImpl(); - this.aclClassIdUtils = new AclClassIdUtils(); - this.fieldAces.setAccessible(true); - this.fieldAcl.setAccessible(true); - } - - private String computeRepeatingSql(String repeatingSql, int requiredRepetitions) { - Assert.isTrue(requiredRepetitions > 0, "requiredRepetitions must be > 0"); - String startSql = this.selectClause; - String endSql = this.orderByClause; - StringBuilder sqlStringBldr = new StringBuilder( - startSql.length() + endSql.length() + requiredRepetitions * (repeatingSql.length() + 4)); - sqlStringBldr.append(startSql); - for (int i = 1; i <= requiredRepetitions; i++) { - sqlStringBldr.append(repeatingSql); - if (i != requiredRepetitions) { - sqlStringBldr.append(" or "); - } - } - sqlStringBldr.append(endSql); - return sqlStringBldr.toString(); - } - - @SuppressWarnings("unchecked") - private List readAces(AclImpl acl) { - try { - return (List) this.fieldAces.get(acl); - } - catch (IllegalAccessException ex) { - throw new IllegalStateException("Could not obtain AclImpl.aces field", ex); - } - } - - private void setAclOnAce(AccessControlEntryImpl ace, AclImpl acl) { - try { - this.fieldAcl.set(ace, acl); - } - catch (IllegalAccessException ex) { - throw new IllegalStateException("Could not or set AclImpl on AccessControlEntryImpl fields", ex); - } - } - - private void setAces(AclImpl acl, List aces) { - try { - this.fieldAces.set(acl, aces); - } - catch (IllegalAccessException ex) { - throw new IllegalStateException("Could not set AclImpl entries", ex); - } - } - - /** - * Locates the primary key IDs specified in "findNow", adding AclImpl instances with - * StubAclParents to the "acls" Map. - * @param acls the AclImpls (with StubAclParents) - * @param findNow Long-based primary keys to retrieve - * @param sids - */ - private void lookupPrimaryKeys(final Map acls, final Set findNow, final List sids) { - Assert.notNull(acls, "ACLs are required"); - Assert.notEmpty(findNow, "Items to find now required"); - String sql = computeRepeatingSql(this.lookupPrimaryKeysWhereClause, findNow.size()); - Set parentsToLookup = this.jdbcTemplate.query(sql, (ps) -> setKeys(ps, findNow), - new ProcessResultSet(acls, sids)); - // Lookup the parents, now that our JdbcTemplate has released the database - // connection (SEC-547) - if (parentsToLookup.size() > 0) { - lookupPrimaryKeys(acls, parentsToLookup, sids); - } - } - - private void setKeys(PreparedStatement ps, Set findNow) throws SQLException { - int i = 0; - for (Long toFind : findNow) { - i++; - ps.setLong(i, toFind); - } - } - - /** - * The main method. - *

- * WARNING: This implementation completely disregards the "sids" argument! Every item - * in the cache is expected to contain all SIDs. If you have serious performance needs - * (e.g. a very large number of SIDs per object identity), you'll probably want to - * develop a custom {@link LookupStrategy} implementation instead. - *

- * The implementation works in batch sizes specified by {@link #batchSize}. - * @param objects the identities to lookup (required) - * @param sids the SIDs for which identities are required (ignored by this - * implementation) - * @return a Map where keys represent the {@link ObjectIdentity} of the - * located {@link Acl} and values are the located {@link Acl} (never null - * although some entries may be missing; this method should not throw - * {@link NotFoundException}, as a chain of {@link LookupStrategy}s may be used to - * automatically create entries if required) - */ - @Override - public final Map readAclsById(List objects, List sids) { - Assert.isTrue(this.batchSize >= 1, "BatchSize must be >= 1"); - Assert.notEmpty(objects, "Objects to lookup required"); - // Map - // contains FULLY loaded Acl objects - Map result = new HashMap<>(); - Set currentBatchToLoad = new HashSet<>(); - for (int i = 0; i < objects.size(); i++) { - final ObjectIdentity oid = objects.get(i); - boolean aclFound = false; - // Check we don't already have this ACL in the results - if (result.containsKey(oid)) { - aclFound = true; - } - // Check cache for the present ACL entry - if (!aclFound) { - Acl acl = this.aclCache.getFromCache(oid); - // Ensure any cached element supports all the requested SIDs - // (they should always, as our base impl doesn't filter on SID) - if (acl != null) { - Assert.state(acl.isSidLoaded(sids), - "Error: SID-filtered element detected when implementation does not perform SID filtering " - + "- have you added something to the cache manually?"); - result.put(acl.getObjectIdentity(), acl); - aclFound = true; - } - } - // Load the ACL from the database - if (!aclFound) { - currentBatchToLoad.add(oid); - } - // Is it time to load from JDBC the currentBatchToLoad? - if ((currentBatchToLoad.size() == this.batchSize) || ((i + 1) == objects.size())) { - if (currentBatchToLoad.size() > 0) { - Map loadedBatch = lookupObjectIdentities(currentBatchToLoad, sids); - // Add loaded batch (all elements 100% initialized) to results - result.putAll(loadedBatch); - // Add the loaded batch to the cache - for (Acl loadedAcl : loadedBatch.values()) { - this.aclCache.putInCache((AclImpl) loadedAcl); - } - currentBatchToLoad.clear(); - } - } - } - return result; - } - - /** - * Looks up a batch of ObjectIdentitys directly from the database. - *

- * The caller is responsible for optimization issues, such as selecting the identities - * to lookup, ensuring the cache doesn't contain them already, and adding the returned - * elements to the cache etc. - *

- * This subclass is required to return fully valid Acls, including - * properly-configured parent ACLs. - */ - private Map lookupObjectIdentities(final Collection objectIdentities, - List sids) { - Assert.notEmpty(objectIdentities, "Must provide identities to lookup"); - - // contains Acls with StubAclParents - Map acls = new HashMap<>(); - - // Make the "acls" map contain all requested objectIdentities - // (including markers to each parent in the hierarchy) - String sql = computeRepeatingSql(this.lookupObjectIdentitiesWhereClause, objectIdentities.size()); - - Set parentsToLookup = this.jdbcTemplate.query(sql, - (ps) -> setupLookupObjectIdentitiesStatement(ps, objectIdentities), new ProcessResultSet(acls, sids)); - - // Lookup the parents, now that our JdbcTemplate has released the database - // connection (SEC-547) - if (parentsToLookup.size() > 0) { - lookupPrimaryKeys(acls, parentsToLookup, sids); - } - - // Finally, convert our "acls" containing StubAclParents into true Acls - Map resultMap = new HashMap<>(); - for (Acl inputAcl : acls.values()) { - Assert.isInstanceOf(AclImpl.class, inputAcl, "Map should have contained an AclImpl"); - Assert.isInstanceOf(Long.class, ((AclImpl) inputAcl).getId(), "Acl.getId() must be Long"); - Acl result = convert(acls, (Long) ((AclImpl) inputAcl).getId()); - resultMap.put(result.getObjectIdentity(), result); - } - - return resultMap; - } - - private void setupLookupObjectIdentitiesStatement(PreparedStatement ps, Collection objectIdentities) - throws SQLException { - int i = 0; - for (ObjectIdentity oid : objectIdentities) { - // Determine prepared statement values for this iteration - String type = oid.getType(); - - // No need to check for nulls, as guaranteed non-null by - // ObjectIdentity.getIdentifier() interface contract - String identifier = oid.getIdentifier().toString(); - - // Inject values - ps.setString((2 * i) + 1, identifier); - ps.setString((2 * i) + 2, type); - i++; - } - } - - /** - * The final phase of converting the Map of AclImpl - * instances which contain StubAclParents into proper, valid - * AclImpls with correct ACL parents. - * @param inputMap the unconverted AclImpls - * @param currentIdentity the currentAcl that we wish to convert (this - * may be - */ - private AclImpl convert(Map inputMap, Long currentIdentity) { - Assert.notEmpty(inputMap, "InputMap required"); - Assert.notNull(currentIdentity, "CurrentIdentity required"); - - // Retrieve this Acl from the InputMap - Acl uncastAcl = inputMap.get(currentIdentity); - Assert.isInstanceOf(AclImpl.class, uncastAcl, "The inputMap contained a non-AclImpl"); - - AclImpl inputAcl = (AclImpl) uncastAcl; - - Acl parent = inputAcl.getParentAcl(); - - if ((parent != null) && parent instanceof StubAclParent) { - // Lookup the parent - StubAclParent stubAclParent = (StubAclParent) parent; - parent = convert(inputMap, stubAclParent.getId()); - } - - // Now we have the parent (if there is one), create the true AclImpl - AclImpl result = new AclImpl(inputAcl.getObjectIdentity(), inputAcl.getId(), this.aclAuthorizationStrategy, - this.grantingStrategy, parent, null, inputAcl.isEntriesInheriting(), inputAcl.getOwner()); - - // Copy the "aces" from the input to the destination - - // Obtain the "aces" from the input ACL - List aces = readAces(inputAcl); - - // Create a list in which to store the "aces" for the "result" AclImpl instance - List acesNew = new ArrayList<>(); - - // Iterate over the "aces" input and replace each nested - // AccessControlEntryImpl.getAcl() with the new "result" AclImpl instance - // This ensures StubAclParent instances are removed, as per SEC-951 - for (AccessControlEntryImpl ace : aces) { - setAclOnAce(ace, result); - acesNew.add(ace); - } - - // Finally, now that the "aces" have been converted to have the "result" AclImpl - // instance, modify the "result" AclImpl instance - setAces(result, acesNew); - - return result; - } - - /** - * Creates a particular implementation of {@link Sid} depending on the arguments. - * @param sid the name of the sid representing its unique identifier. In typical ACL - * database schema it's located in table {@code acl_sid} table, {@code sid} column. - * @param isPrincipal whether it's a user or granted authority like role - * @return the instance of Sid with the {@code sidName} as an identifier - */ - protected Sid createSid(boolean isPrincipal, String sid) { - if (isPrincipal) { - return new PrincipalSid(sid); - } - return new GrantedAuthoritySid(sid); - } - - /** - * Sets the {@code PermissionFactory} instance which will be used to convert loaded - * permission data values to {@code Permission}s. A {@code DefaultPermissionFactory} - * will be used by default. - * @param permissionFactory - */ - public final void setPermissionFactory(PermissionFactory permissionFactory) { - this.permissionFactory = permissionFactory; - } - - public final void setBatchSize(int batchSize) { - this.batchSize = batchSize; - } - - /** - * The SQL for the select clause. If customizing in order to modify column names, - * schema etc, the other SQL customization fields must also be set to match. - * @param selectClause the select clause, which defaults to - * {@link #DEFAULT_SELECT_CLAUSE}. - */ - public final void setSelectClause(String selectClause) { - this.selectClause = selectClause; - } - - /** - * The SQL for the where clause used in the lookupPrimaryKey method. - */ - public final void setLookupPrimaryKeysWhereClause(String lookupPrimaryKeysWhereClause) { - this.lookupPrimaryKeysWhereClause = lookupPrimaryKeysWhereClause; - } - - /** - * The SQL for the where clause used in the lookupObjectIdentities method. - */ - public final void setLookupObjectIdentitiesWhereClause(String lookupObjectIdentitiesWhereClause) { - this.lookupObjectIdentitiesWhereClause = lookupObjectIdentitiesWhereClause; - } - - /** - * The SQL for the "order by" clause used in both queries. - */ - public final void setOrderByClause(String orderByClause) { - this.orderByClause = orderByClause; - } - - public final void setAclClassIdSupported(boolean aclClassIdSupported) { - if (aclClassIdSupported) { - Assert.isTrue(this.selectClause.equals(DEFAULT_SELECT_CLAUSE), - "Cannot set aclClassIdSupported and override the select clause; " - + "just override the select clause"); - this.selectClause = DEFAULT_ACL_CLASS_ID_SELECT_CLAUSE; - } - } - - public final void setObjectIdentityGenerator(ObjectIdentityGenerator objectIdentityGenerator) { - Assert.notNull(objectIdentityGenerator, "objectIdentityGenerator cannot be null"); - this.objectIdentityGenerator = objectIdentityGenerator; - } - - public final void setConversionService(ConversionService conversionService) { - this.aclClassIdUtils = new AclClassIdUtils(conversionService); - } - - private class ProcessResultSet implements ResultSetExtractor> { - - private final Map acls; - - private final List sids; - - ProcessResultSet(Map acls, List sids) { - Assert.notNull(acls, "ACLs cannot be null"); - this.acls = acls; - this.sids = sids; // can be null - } - - /** - * Implementation of {@link ResultSetExtractor#extractData(ResultSet)}. Creates an - * {@link Acl} for each row in the {@link ResultSet} and ensures it is in member - * field acls. Any {@link Acl} with a parent will have the parents id - * returned in a set. The returned set of ids may requires further processing. - * @param rs The {@link ResultSet} to be processed - * @return a list of parent IDs remaining to be looked up (may be empty, but never - * null) - * @throws SQLException - */ - @Override - public Set extractData(ResultSet rs) throws SQLException { - Set parentIdsToLookup = new HashSet<>(); // Set of parent_id Longs - - while (rs.next()) { - // Convert current row into an Acl (albeit with a StubAclParent) - convertCurrentResultIntoObject(this.acls, rs); - - // Figure out if this row means we need to lookup another parent - long parentId = rs.getLong("parent_object"); - - if (parentId != 0) { - // See if it's already in the "acls" - if (this.acls.containsKey(parentId)) { - continue; // skip this while iteration - } - - // Now try to find it in the cache - MutableAcl cached = BasicLookupStrategy.this.aclCache.getFromCache(parentId); - if ((cached == null) || !cached.isSidLoaded(this.sids)) { - parentIdsToLookup.add(parentId); - } - else { - // Pop into the acls map, so our convert method doesn't - // need to deal with an unsynchronized AclCache - this.acls.put(cached.getId(), cached); - } - } - } - - // Return the parents left to lookup to the caller - return parentIdsToLookup; - } - - /** - * Accepts the current ResultSet row, and converts it into an - * AclImpl that contains a StubAclParent - * @param acls the Map we should add the converted Acl to - * @param rs the ResultSet focused on a current row - * @throws SQLException if something goes wrong converting values - * @throws ConversionException if can't convert to the desired Java type - */ - private void convertCurrentResultIntoObject(Map acls, ResultSet rs) throws SQLException { - Long id = rs.getLong("acl_id"); - - // If we already have an ACL for this ID, just create the ACE - Acl acl = acls.get(id); - - if (acl == null) { - // Make an AclImpl and pop it into the Map - - // If the Java type is a String, check to see if we can convert it to the - // target id type, e.g. UUID. - Serializable identifier = (Serializable) rs.getObject("object_id_identity"); - identifier = BasicLookupStrategy.this.aclClassIdUtils.identifierFrom(identifier, rs); - ObjectIdentity objectIdentity = BasicLookupStrategy.this.objectIdentityGenerator - .createObjectIdentity(identifier, rs.getString("class")); - - Acl parentAcl = null; - long parentAclId = rs.getLong("parent_object"); - - if (parentAclId != 0) { - parentAcl = new StubAclParent(parentAclId); - } - - boolean entriesInheriting = rs.getBoolean("entries_inheriting"); - Sid owner = createSid(rs.getBoolean("acl_principal"), rs.getString("acl_sid")); - - acl = new AclImpl(objectIdentity, id, BasicLookupStrategy.this.aclAuthorizationStrategy, - BasicLookupStrategy.this.grantingStrategy, parentAcl, null, entriesInheriting, owner); - - acls.put(id, acl); - } - - // Add an extra ACE to the ACL (ORDER BY maintains the ACE list order) - // It is permissible to have no ACEs in an ACL (which is detected by a null - // ACE_SID) - if (rs.getString("ace_sid") != null) { - Long aceId = rs.getLong("ace_id"); - Sid recipient = createSid(rs.getBoolean("ace_principal"), rs.getString("ace_sid")); - - int mask = rs.getInt("mask"); - Permission permission = BasicLookupStrategy.this.permissionFactory.buildFromMask(mask); - boolean granting = rs.getBoolean("granting"); - boolean auditSuccess = rs.getBoolean("audit_success"); - boolean auditFailure = rs.getBoolean("audit_failure"); - - AccessControlEntryImpl ace = new AccessControlEntryImpl(aceId, acl, recipient, permission, granting, - auditSuccess, auditFailure); - - // Field acesField = FieldUtils.getField(AclImpl.class, "aces"); - List aces = readAces((AclImpl) acl); - - // Add the ACE if it doesn't already exist in the ACL.aces field - if (!aces.contains(ace)) { - aces.add(ace); - } - } - } - - } - - private static class StubAclParent implements Acl { - - private final Long id; - - StubAclParent(Long id) { - this.id = id; - } - - Long getId() { - return this.id; - } - - @Override - public List getEntries() { - throw new UnsupportedOperationException("Stub only"); - } - - @Override - public ObjectIdentity getObjectIdentity() { - throw new UnsupportedOperationException("Stub only"); - } - - @Override - public Sid getOwner() { - throw new UnsupportedOperationException("Stub only"); - } - - @Override - public Acl getParentAcl() { - throw new UnsupportedOperationException("Stub only"); - } - - @Override - public boolean isEntriesInheriting() { - throw new UnsupportedOperationException("Stub only"); - } - - @Override - public boolean isGranted(List permission, List sids, boolean administrativeMode) - throws NotFoundException, UnloadedSidException { - throw new UnsupportedOperationException("Stub only"); - } - - @Override - public boolean isSidLoaded(List sids) { - throw new UnsupportedOperationException("Stub only"); - } - - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java b/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java deleted file mode 100644 index f8dbb687e66..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcAclService.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2004, 2005, 2006, 2017, 2018 Acegi Technology Pty Limited - * - * 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.jdbc; - -import java.io.Serializable; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import javax.sql.DataSource; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import org.springframework.core.convert.ConversionService; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.security.acls.domain.ObjectIdentityRetrievalStrategyImpl; -import org.springframework.security.acls.model.Acl; -import org.springframework.security.acls.model.AclService; -import org.springframework.security.acls.model.NotFoundException; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.ObjectIdentityGenerator; -import org.springframework.security.acls.model.Sid; -import org.springframework.util.Assert; - -/** - * Simple JDBC-based implementation of AclService. - *

- * Requires the "dirty" flags in {@link org.springframework.security.acls.domain.AclImpl} - * and {@link org.springframework.security.acls.domain.AccessControlEntryImpl} to be set, - * so that the implementation can detect changed parameters easily. - * - * @author Ben Alex - */ -public class JdbcAclService implements AclService { - - protected static final Log log = LogFactory.getLog(JdbcAclService.class); - - private static final String DEFAULT_SELECT_ACL_CLASS_COLUMNS = "class.class as class"; - - private static final String DEFAULT_SELECT_ACL_CLASS_COLUMNS_WITH_ID_TYPE = DEFAULT_SELECT_ACL_CLASS_COLUMNS - + ", class.class_id_type as class_id_type"; - - private static final String DEFAULT_SELECT_ACL_WITH_PARENT_SQL = "select obj.object_id_identity as obj_id, " - + DEFAULT_SELECT_ACL_CLASS_COLUMNS - + " from acl_object_identity obj, acl_object_identity parent, acl_class class " - + "where obj.parent_object = parent.id and obj.object_id_class = class.id " - + "and parent.object_id_identity = ? and parent.object_id_class = (" - + "select id FROM acl_class where acl_class.class = ?)"; - - private static final String DEFAULT_SELECT_ACL_WITH_PARENT_SQL_WITH_CLASS_ID_TYPE = "select obj.object_id_identity as obj_id, " - + DEFAULT_SELECT_ACL_CLASS_COLUMNS_WITH_ID_TYPE - + " from acl_object_identity obj, acl_object_identity parent, acl_class class " - + "where obj.parent_object = parent.id and obj.object_id_class = class.id " - + "and parent.object_id_identity = ? and parent.object_id_class = (" - + "select id FROM acl_class where acl_class.class = ?)"; - - protected final JdbcOperations jdbcOperations; - - private final LookupStrategy lookupStrategy; - - private boolean aclClassIdSupported; - - private String findChildrenSql = DEFAULT_SELECT_ACL_WITH_PARENT_SQL; - - private AclClassIdUtils aclClassIdUtils; - - private ObjectIdentityGenerator objectIdentityGenerator; - - public JdbcAclService(DataSource dataSource, LookupStrategy lookupStrategy) { - this(new JdbcTemplate(dataSource), lookupStrategy); - } - - public JdbcAclService(JdbcOperations jdbcOperations, LookupStrategy lookupStrategy) { - Assert.notNull(jdbcOperations, "JdbcOperations required"); - Assert.notNull(lookupStrategy, "LookupStrategy required"); - this.jdbcOperations = jdbcOperations; - this.lookupStrategy = lookupStrategy; - this.aclClassIdUtils = new AclClassIdUtils(); - this.objectIdentityGenerator = new ObjectIdentityRetrievalStrategyImpl(); - } - - @Override - public List findChildren(ObjectIdentity parentIdentity) { - Object[] args = { parentIdentity.getIdentifier().toString(), parentIdentity.getType() }; - List objects = this.jdbcOperations.query(this.findChildrenSql, - (rs, rowNum) -> mapObjectIdentityRow(rs), args); - return (!objects.isEmpty()) ? objects : null; - } - - private ObjectIdentity mapObjectIdentityRow(ResultSet rs) throws SQLException { - String javaType = rs.getString("class"); - Serializable identifier = (Serializable) rs.getObject("obj_id"); - identifier = this.aclClassIdUtils.identifierFrom(identifier, rs); - return this.objectIdentityGenerator.createObjectIdentity(identifier, javaType); - } - - @Override - public Acl readAclById(ObjectIdentity object, List sids) throws NotFoundException { - Map map = readAclsById(Collections.singletonList(object), sids); - Assert.isTrue(map.containsKey(object), - () -> "There should have been an Acl entry for ObjectIdentity " + object); - return map.get(object); - } - - @Override - public Acl readAclById(ObjectIdentity object) throws NotFoundException { - return readAclById(object, null); - } - - @Override - public Map readAclsById(List objects) throws NotFoundException { - return readAclsById(objects, null); - } - - @Override - public Map readAclsById(List objects, List sids) - throws NotFoundException { - Map result = this.lookupStrategy.readAclsById(objects, sids); - // Check every requested object identity was found (throw NotFoundException if - // needed) - for (ObjectIdentity oid : objects) { - if (!result.containsKey(oid)) { - throw new NotFoundException("Unable to find ACL information for object identity '" + oid + "'"); - } - } - return result; - } - - /** - * Allows customization of the SQL query used to find child object identities. - * @param findChildrenSql - */ - public void setFindChildrenQuery(String findChildrenSql) { - this.findChildrenSql = findChildrenSql; - } - - public void setAclClassIdSupported(boolean aclClassIdSupported) { - this.aclClassIdSupported = aclClassIdSupported; - if (aclClassIdSupported) { - // Change the default children select if it hasn't been overridden - if (this.findChildrenSql.equals(DEFAULT_SELECT_ACL_WITH_PARENT_SQL)) { - this.findChildrenSql = DEFAULT_SELECT_ACL_WITH_PARENT_SQL_WITH_CLASS_ID_TYPE; - } - else { - log.debug("Find children statement has already been overridden, so not overridding the default"); - } - } - } - - public void setConversionService(ConversionService conversionService) { - this.aclClassIdUtils = new AclClassIdUtils(conversionService); - } - - public void setObjectIdentityGenerator(ObjectIdentityGenerator objectIdentityGenerator) { - Assert.notNull(objectIdentityGenerator, "objectIdentityGenerator cannot be null"); - this.objectIdentityGenerator = objectIdentityGenerator; - } - - protected boolean isAclClassIdSupported() { - return this.aclClassIdSupported; - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java b/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java deleted file mode 100644 index 9b8eb5acbc2..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/jdbc/JdbcMutableAclService.java +++ /dev/null @@ -1,490 +0,0 @@ -/* - * Copyright 2004, 2005, 2006, 2017, 2018 Acegi Technology Pty Limited - * - * 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.jdbc; - -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.List; - -import javax.sql.DataSource; - -import org.springframework.dao.DataAccessException; -import org.springframework.jdbc.core.BatchPreparedStatementSetter; -import org.springframework.security.acls.domain.AccessControlEntryImpl; -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.AclCache; -import org.springframework.security.acls.model.AlreadyExistsException; -import org.springframework.security.acls.model.ChildrenExistException; -import org.springframework.security.acls.model.MutableAcl; -import org.springframework.security.acls.model.MutableAclService; -import org.springframework.security.acls.model.NotFoundException; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.Sid; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.transaction.support.TransactionSynchronizationManager; -import org.springframework.util.Assert; - -/** - * Provides a base JDBC implementation of {@link MutableAclService}. - *

- * 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 - * {@link #setClassIdentityQuery(String) classIdentityQuery} properties appropriately. The - * 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. - *

- * See the appendix of the Spring Security reference manual for more information on the - * expected schema and how it is used. Information on using PostgreSQL is also included. - * - * @author Ben Alex - * @author Johannes Zlattinger - */ -public class JdbcMutableAclService extends JdbcAclService implements MutableAclService { - - private static final String DEFAULT_INSERT_INTO_ACL_CLASS = "insert into acl_class (class) values (?)"; - - private static final String DEFAULT_INSERT_INTO_ACL_CLASS_WITH_ID = "insert into acl_class (class, class_id_type) values (?, ?)"; - - private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder - .getContextHolderStrategy(); - - private boolean foreignKeysInDatabase = true; - - private final AclCache aclCache; - - private String deleteEntryByObjectIdentityForeignKey = "delete from acl_entry where acl_object_identity=?"; - - private String deleteObjectIdentityByPrimaryKey = "delete from acl_object_identity where id=?"; - - private String classIdentityQuery = "call identity()"; - - private String sidIdentityQuery = "call identity()"; - - private String insertClass = DEFAULT_INSERT_INTO_ACL_CLASS; - - private String insertEntry = "insert into acl_entry " - + "(acl_object_identity, ace_order, sid, mask, granting, audit_success, audit_failure)" - + "values (?, ?, ?, ?, ?, ?, ?)"; - - private String insertObjectIdentity = "insert into acl_object_identity " - + "(object_id_class, object_id_identity, owner_sid, entries_inheriting) " + "values (?, ?, ?, ?)"; - - private String insertSid = "insert into acl_sid (principal, sid) values (?, ?)"; - - private String selectClassPrimaryKey = "select id from acl_class where class=?"; - - private String selectObjectIdentityPrimaryKey = "select acl_object_identity.id from acl_object_identity, acl_class " - + "where acl_object_identity.object_id_class = acl_class.id and acl_class.class=? " - + "and acl_object_identity.object_id_identity = ?"; - - private String selectSidPrimaryKey = "select id from acl_sid where principal=? and sid=?"; - - private String updateObjectIdentity = "update acl_object_identity set " - + "parent_object = ?, owner_sid = ?, entries_inheriting = ?" + " where id = ?"; - - public JdbcMutableAclService(DataSource dataSource, LookupStrategy lookupStrategy, AclCache aclCache) { - super(dataSource, lookupStrategy); - Assert.notNull(aclCache, "AclCache required"); - this.aclCache = aclCache; - } - - @Override - public MutableAcl createAcl(ObjectIdentity objectIdentity) throws AlreadyExistsException { - Assert.notNull(objectIdentity, "Object Identity required"); - - // Check this object identity hasn't already been persisted - if (retrieveObjectIdentityPrimaryKey(objectIdentity) != null) { - throw new AlreadyExistsException("Object identity '" + objectIdentity + "' already exists"); - } - - // Need to retrieve the current principal, in order to know who "owns" this ACL - // (can be changed later on) - Authentication auth = this.securityContextHolderStrategy.getContext().getAuthentication(); - PrincipalSid sid = new PrincipalSid(auth); - - // Create the acl_object_identity row - createObjectIdentity(objectIdentity, sid); - - // Retrieve the ACL via superclass (ensures cache registration, proper retrieval - // etc) - Acl acl = readAclById(objectIdentity); - Assert.isInstanceOf(MutableAcl.class, acl, "MutableAcl should be been returned"); - - return (MutableAcl) acl; - } - - /** - * Creates a new row in acl_entry for every ACE defined in the passed MutableAcl - * object. - * @param acl containing the ACEs to insert - */ - protected void createEntries(final MutableAcl acl) { - if (acl.getEntries().isEmpty()) { - return; - } - this.jdbcOperations.batchUpdate(this.insertEntry, new BatchPreparedStatementSetter() { - - @Override - public int getBatchSize() { - return acl.getEntries().size(); - } - - @Override - public void setValues(PreparedStatement stmt, int i) throws SQLException { - AccessControlEntry entry_ = acl.getEntries().get(i); - Assert.isTrue(entry_ instanceof AccessControlEntryImpl, "Unknown ACE class"); - AccessControlEntryImpl entry = (AccessControlEntryImpl) entry_; - - stmt.setLong(1, (Long) acl.getId()); - stmt.setInt(2, i); - stmt.setLong(3, createOrRetrieveSidPrimaryKey(entry.getSid(), true)); - stmt.setInt(4, entry.getPermission().getMask()); - stmt.setBoolean(5, entry.isGranting()); - stmt.setBoolean(6, entry.isAuditSuccess()); - stmt.setBoolean(7, entry.isAuditFailure()); - } - - }); - } - - /** - * Creates an entry in the acl_object_identity table for the passed ObjectIdentity. - * The Sid is also necessary, as acl_object_identity has defined the sid column as - * non-null. - * @param object to represent an acl_object_identity for - * @param owner for the SID column (will be created if there is no acl_sid entry for - * this particular Sid already) - */ - protected void createObjectIdentity(ObjectIdentity object, Sid owner) { - Long sidId = createOrRetrieveSidPrimaryKey(owner, true); - Long classId = createOrRetrieveClassPrimaryKey(object.getType(), true, object.getIdentifier().getClass()); - this.jdbcOperations.update(this.insertObjectIdentity, classId, object.getIdentifier().toString(), sidId, - Boolean.TRUE); - } - - /** - * Retrieves the primary key from {@code acl_class}, creating a new row if needed and - * the {@code allowCreate} property is {@code true}. - * @param type to find or create an entry for (often the fully-qualified class name) - * @param allowCreate true if creation is permitted if not found - * @return the primary key or null if not found - */ - protected Long createOrRetrieveClassPrimaryKey(String type, boolean allowCreate, Class idType) { - List classIds = this.jdbcOperations.queryForList(this.selectClassPrimaryKey, Long.class, type); - - if (!classIds.isEmpty()) { - return classIds.get(0); - } - - if (allowCreate) { - if (!isAclClassIdSupported()) { - this.jdbcOperations.update(this.insertClass, type); - } - else { - this.jdbcOperations.update(this.insertClass, type, idType.getCanonicalName()); - } - Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), "Transaction must be running"); - return this.jdbcOperations.queryForObject(this.classIdentityQuery, Long.class); - } - - return null; - } - - /** - * Retrieves the primary key from acl_sid, creating a new row if needed and the - * allowCreate property is true. - * @param sid to find or create - * @param allowCreate true if creation is permitted if not found - * @return the primary key or null if not found - * @throws IllegalArgumentException if the Sid is not a recognized - * implementation. - */ - protected Long createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) { - Assert.notNull(sid, "Sid required"); - if (sid instanceof PrincipalSid) { - String sidName = ((PrincipalSid) sid).getPrincipal(); - return createOrRetrieveSidPrimaryKey(sidName, true, allowCreate); - } - if (sid instanceof GrantedAuthoritySid) { - String sidName = ((GrantedAuthoritySid) sid).getGrantedAuthority(); - return createOrRetrieveSidPrimaryKey(sidName, false, allowCreate); - } - throw new IllegalArgumentException("Unsupported implementation of Sid"); - } - - /** - * Retrieves the primary key from acl_sid, creating a new row if needed and the - * allowCreate property is true. - * @param sidName name of Sid to find or to create - * @param sidIsPrincipal whether it's a user or granted authority like role - * @param allowCreate true if creation is permitted if not found - * @return the primary key or null if not found - */ - protected Long createOrRetrieveSidPrimaryKey(String sidName, boolean sidIsPrincipal, boolean allowCreate) { - List sidIds = this.jdbcOperations.queryForList(this.selectSidPrimaryKey, Long.class, sidIsPrincipal, - sidName); - if (!sidIds.isEmpty()) { - return sidIds.get(0); - } - if (allowCreate) { - this.jdbcOperations.update(this.insertSid, sidIsPrincipal, sidName); - Assert.isTrue(TransactionSynchronizationManager.isSynchronizationActive(), "Transaction must be running"); - return this.jdbcOperations.queryForObject(this.sidIdentityQuery, Long.class); - } - return null; - } - - @Override - public void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren) throws ChildrenExistException { - Assert.notNull(objectIdentity, "Object Identity required"); - Assert.notNull(objectIdentity.getIdentifier(), "Object Identity doesn't provide an identifier"); - if (deleteChildren) { - List children = findChildren(objectIdentity); - if (children != null) { - for (ObjectIdentity child : children) { - deleteAcl(child, true); - } - } - } - else { - if (!this.foreignKeysInDatabase) { - // We need to perform a manual verification for what a FK would normally - // do. We generally don't do this, in the interests of deadlock management - List children = findChildren(objectIdentity); - if (children != null) { - throw new ChildrenExistException( - "Cannot delete '" + objectIdentity + "' (has " + children.size() + " children)"); - } - } - } - - Long oidPrimaryKey = retrieveObjectIdentityPrimaryKey(objectIdentity); - - // Delete this ACL's ACEs in the acl_entry table - deleteEntries(oidPrimaryKey); - - // Delete this ACL's acl_object_identity row - deleteObjectIdentity(oidPrimaryKey); - - // Clear the cache - this.aclCache.evictFromCache(objectIdentity); - } - - /** - * Deletes all ACEs defined in the acl_entry table belonging to the presented - * ObjectIdentity primary key. - * @param oidPrimaryKey the rows in acl_entry to delete - */ - protected void deleteEntries(Long oidPrimaryKey) { - this.jdbcOperations.update(this.deleteEntryByObjectIdentityForeignKey, oidPrimaryKey); - } - - /** - * Deletes a single row from acl_object_identity that is associated with the presented - * ObjectIdentity primary key. - *

- * We do not delete any entries from acl_class, even if no classes are using that - * class any longer. This is a deadlock avoidance approach. - * @param oidPrimaryKey to delete the acl_object_identity - */ - protected void deleteObjectIdentity(Long oidPrimaryKey) { - // Delete the acl_object_identity row - this.jdbcOperations.update(this.deleteObjectIdentityByPrimaryKey, oidPrimaryKey); - } - - /** - * Retrieves the primary key from the acl_object_identity table for the passed - * ObjectIdentity. Unlike some other methods in this implementation, this method will - * NOT create a row (use {@link #createObjectIdentity(ObjectIdentity, Sid)} instead). - * @param oid to find - * @return the object identity or null if not found - */ - protected Long retrieveObjectIdentityPrimaryKey(ObjectIdentity oid) { - try { - return this.jdbcOperations.queryForObject(this.selectObjectIdentityPrimaryKey, Long.class, oid.getType(), - oid.getIdentifier().toString()); - } - catch (DataAccessException notFound) { - return null; - } - } - - /** - * This implementation will simply delete all ACEs in the database and recreate them - * on each invocation of this method. A more comprehensive implementation might use - * dirty state checking, or more likely use ORM capabilities for create, update and - * delete operations of {@link MutableAcl}. - */ - @Override - public MutableAcl updateAcl(MutableAcl acl) throws NotFoundException { - Assert.notNull(acl.getId(), "Object Identity doesn't provide an identifier"); - - // Delete this ACL's ACEs in the acl_entry table - deleteEntries(retrieveObjectIdentityPrimaryKey(acl.getObjectIdentity())); - - // Create this ACL's ACEs in the acl_entry table - createEntries(acl); - - // Change the mutable columns in acl_object_identity - updateObjectIdentity(acl); - - // Clear the cache, including children - clearCacheIncludingChildren(acl.getObjectIdentity()); - - // Retrieve the ACL via superclass (ensures cache registration, proper retrieval - // etc) - return (MutableAcl) super.readAclById(acl.getObjectIdentity()); - } - - private void clearCacheIncludingChildren(ObjectIdentity objectIdentity) { - Assert.notNull(objectIdentity, "ObjectIdentity required"); - List children = findChildren(objectIdentity); - if (children != null) { - for (ObjectIdentity child : children) { - clearCacheIncludingChildren(child); - } - } - this.aclCache.evictFromCache(objectIdentity); - } - - /** - * Updates an existing acl_object_identity row, with new information presented in the - * passed MutableAcl object. Also will create an acl_sid entry if needed for the Sid - * that owns the MutableAcl. - * @param acl to modify (a row must already exist in acl_object_identity) - * @throws NotFoundException if the ACL could not be found to update. - */ - protected void updateObjectIdentity(MutableAcl acl) { - Long parentId = null; - if (acl.getParentAcl() != null) { - Assert.isInstanceOf(ObjectIdentityImpl.class, acl.getParentAcl().getObjectIdentity(), - "Implementation only supports ObjectIdentityImpl"); - ObjectIdentityImpl oii = (ObjectIdentityImpl) acl.getParentAcl().getObjectIdentity(); - parentId = retrieveObjectIdentityPrimaryKey(oii); - } - Assert.notNull(acl.getOwner(), "Owner is required in this implementation"); - Long ownerSid = createOrRetrieveSidPrimaryKey(acl.getOwner(), true); - int count = this.jdbcOperations.update(this.updateObjectIdentity, parentId, ownerSid, acl.isEntriesInheriting(), - acl.getId()); - if (count != 1) { - throw new NotFoundException("Unable to locate ACL to update"); - } - } - - /** - * Sets the query that will be used to retrieve the identity of a newly created row in - * the acl_class table. - * @param classIdentityQuery the query, which should return the identifier. Defaults - * to call identity() - */ - public void setClassIdentityQuery(String classIdentityQuery) { - Assert.hasText(classIdentityQuery, "New classIdentityQuery query is required"); - this.classIdentityQuery = classIdentityQuery; - } - - /** - * Sets the query that will be used to retrieve the identity of a newly created row in - * the acl_sid table. - * @param sidIdentityQuery the query, which should return the identifier. Defaults to - * call identity() - */ - public void setSidIdentityQuery(String sidIdentityQuery) { - Assert.hasText(sidIdentityQuery, "New sidIdentityQuery query is required"); - this.sidIdentityQuery = sidIdentityQuery; - } - - public void setDeleteEntryByObjectIdentityForeignKeySql(String deleteEntryByObjectIdentityForeignKey) { - this.deleteEntryByObjectIdentityForeignKey = deleteEntryByObjectIdentityForeignKey; - } - - public void setDeleteObjectIdentityByPrimaryKeySql(String deleteObjectIdentityByPrimaryKey) { - this.deleteObjectIdentityByPrimaryKey = deleteObjectIdentityByPrimaryKey; - } - - public void setInsertClassSql(String insertClass) { - this.insertClass = insertClass; - } - - public void setInsertEntrySql(String insertEntry) { - this.insertEntry = insertEntry; - } - - public void setInsertObjectIdentitySql(String insertObjectIdentity) { - this.insertObjectIdentity = insertObjectIdentity; - } - - public void setInsertSidSql(String insertSid) { - this.insertSid = insertSid; - } - - public void setClassPrimaryKeyQuery(String selectClassPrimaryKey) { - this.selectClassPrimaryKey = selectClassPrimaryKey; - } - - public void setObjectIdentityPrimaryKeyQuery(String selectObjectIdentityPrimaryKey) { - this.selectObjectIdentityPrimaryKey = selectObjectIdentityPrimaryKey; - } - - public void setSidPrimaryKeyQuery(String selectSidPrimaryKey) { - this.selectSidPrimaryKey = selectSidPrimaryKey; - } - - public void setUpdateObjectIdentity(String updateObjectIdentity) { - this.updateObjectIdentity = updateObjectIdentity; - } - - /** - * @param foreignKeysInDatabase if false this class will perform additional FK - * constrain checking, which may cause deadlocks (the default is true, so deadlocks - * are avoided but the database is expected to enforce FKs) - */ - public void setForeignKeysInDatabase(boolean foreignKeysInDatabase) { - this.foreignKeysInDatabase = foreignKeysInDatabase; - } - - @Override - public void setAclClassIdSupported(boolean aclClassIdSupported) { - super.setAclClassIdSupported(aclClassIdSupported); - if (aclClassIdSupported) { - // Change the default insert if it hasn't been overridden - if (this.insertClass.equals(DEFAULT_INSERT_INTO_ACL_CLASS)) { - this.insertClass = DEFAULT_INSERT_INTO_ACL_CLASS_WITH_ID; - } - else { - log.debug("Insert class statement has already been overridden, so not overridding the default"); - } - } - } - - /** - * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use - * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}. - * - * @since 5.8 - */ - public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) { - Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null"); - this.securityContextHolderStrategy = securityContextHolderStrategy; - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/jdbc/LookupStrategy.java b/acl/src/main/java/org/springframework/security/acls/jdbc/LookupStrategy.java deleted file mode 100644 index adc6c6aef1c..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/jdbc/LookupStrategy.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.jdbc; - -import java.util.List; -import java.util.Map; - -import org.springframework.security.acls.model.Acl; -import org.springframework.security.acls.model.NotFoundException; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.Sid; - -/** - * Performs lookups for {@link org.springframework.security.acls.model.AclService}. - * - * @author Ben Alex - */ -public interface LookupStrategy { - - /** - * Perform database-specific optimized lookup. - * @param objects the identities to lookup (required) - * @param sids the SIDs for which identities are required (may be null - - * implementations may elect not to provide SID optimisations) - * @return a Map where keys represent the {@link ObjectIdentity} of the - * located {@link Acl} and values are the located {@link Acl} (never null - * although some entries may be missing; this method should not throw - * {@link NotFoundException}, as a chain of {@link LookupStrategy}s may be used to - * automatically create entries if required) - */ - Map readAclsById(List objects, List sids); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/jdbc/package-info.java b/acl/src/main/java/org/springframework/security/acls/jdbc/package-info.java deleted file mode 100644 index 35ad8a28674..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/jdbc/package-info.java +++ /dev/null @@ -1,20 +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. - */ - -/** - * JDBC-based persistence of ACL information - */ -package org.springframework.security.acls.jdbc; diff --git a/acl/src/main/java/org/springframework/security/acls/model/AccessControlEntry.java b/acl/src/main/java/org/springframework/security/acls/model/AccessControlEntry.java deleted file mode 100644 index d8c8e286f67..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/AccessControlEntry.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -import java.io.Serializable; - -/** - * Represents an individual permission assignment within an {@link Acl}. - * - *

- * Instances MUST be immutable, as they are returned by Acl and should not - * allow client modification. - *

- * - * @author Ben Alex - */ -public interface AccessControlEntry extends Serializable { - - Acl getAcl(); - - /** - * Obtains an identifier that represents this ACE. - * @return the identifier, or null if unsaved - */ - Serializable getId(); - - Permission getPermission(); - - Sid getSid(); - - /** - * Indicates the permission is being granted to the relevant Sid. If false, indicates - * the permission is being revoked/blocked. - * @return true if being granted, false otherwise - */ - boolean isGranting(); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/Acl.java b/acl/src/main/java/org/springframework/security/acls/model/Acl.java deleted file mode 100644 index 8128c9e0c1a..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/Acl.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -import java.io.Serializable; -import java.util.List; - -/** - * Represents an access control list (ACL) for a domain object. - * - *

- * An Acl represents all ACL entries for a given domain object. In order to avoid - * needing references to the domain object itself, this interface handles indirection - * between a domain object and an ACL object identity via the - * {@link org.springframework.security.acls.model.ObjectIdentity} interface. - *

- * - *

- * Implementing classes may elect to return instances that represent - * {@link org.springframework.security.acls.model.Permission} information for either some - * OR all {@link org.springframework.security.acls.model.Sid} instances. Therefore, an - * instance may NOT necessarily contain ALL Sids for a given domain object. - *

- * - * @author Ben Alex - */ -public interface Acl extends Serializable { - - /** - * Returns all of the entries represented by the present Acl. Entries - * associated with the Acl parents are not returned. - * - *

- * This method is typically used for administrative purposes. - *

- * - *

- * The order that entries appear in the array is important for methods declared in the - * {@link MutableAcl} interface. Furthermore, some implementations MAY use ordering as - * part of advanced permission checking. - *

- * - *

- * Do NOT use this method for making authorization decisions. Instead use - * {@link #isGranted(List, List, boolean)}. - *

- * - *

- * This method must operate correctly even if the Acl only represents a - * subset of Sids. The caller is responsible for correctly handling the - * result if only a subset of Sids is represented. - *

- * @return the list of entries represented by the Acl, or null if - * there are no entries presently associated with this Acl. - */ - List getEntries(); - - /** - * Obtains the domain object this Acl provides entries for. This is immutable - * once an Acl is created. - * @return the object identity (never null) - */ - ObjectIdentity getObjectIdentity(); - - /** - * Determines the owner of the Acl. The meaning of ownership varies by - * implementation and is unspecified. - * @return the owner (may be null if the implementation does not use - * ownership concepts) - */ - Sid getOwner(); - - /** - * A domain object may have a parent for the purpose of ACL inheritance. If there is a - * parent, its ACL can be accessed via this method. In turn, the parent's parent - * (grandparent) can be accessed and so on. - * - *

- * This method solely represents the presence of a navigation hierarchy between the - * parent Acl and this Acl. For actual inheritance to take place, - * the {@link #isEntriesInheriting()} must also be true. - *

- * - *

- * This method must operate correctly even if the Acl only represents a - * subset of Sids. The caller is responsible for correctly handling the - * result if only a subset of Sids is represented. - *

- * @return the parent Acl (may be null if this Acl does not - * have a parent) - */ - Acl getParentAcl(); - - /** - * Indicates whether the ACL entries from the {@link #getParentAcl()} should flow down - * into the current Acl. - *

- * The mere link between an Acl and a parent Acl on its own is - * insufficient to cause ACL entries to inherit down. This is because a domain object - * may wish to have entirely independent entries, but maintain the link with the - * parent for navigation purposes. Thus, this method denotes whether or not the - * navigation relationship also extends to the actual inheritance of entries. - *

- * @return true if parent ACL entries inherit into the current Acl - */ - boolean isEntriesInheriting(); - - /** - * This is the actual authorization logic method, and must be used whenever ACL - * authorization decisions are required. - * - *

- * An array of Sids are presented, representing security identifies of the - * current principal. In addition, an array of Permissions is presented which - * will have one or more bits set in order to indicate the permissions needed for an - * affirmative authorization decision. An array is presented because holding - * any of the Permissions inside the array will be sufficient for an - * affirmative authorization. - *

- * - *

- * The actual approach used to make authorization decisions is left to the - * implementation and is not specified by this interface. For example, an - * implementation MAY search the current ACL in the order the ACL entries - * have been stored. If a single entry is found that has the same active bits as are - * shown in a passed Permission, that entry's grant or deny state may - * determine the authorization decision. If the case of a deny state, the deny - * decision will only be relevant if all other Permissions passed in the - * array have also been unsuccessfully searched. If no entry is found that match the - * bits in the current ACL, provided that {@link #isEntriesInheriting()} is - * true, the authorization decision may be passed to the parent ACL. If there - * is no matching entry, the implementation MAY throw an exception, or make a - * predefined authorization decision. - *

- * - *

- * This method must operate correctly even if the Acl only represents a - * subset of Sids, although the implementation is permitted to throw one of - * the signature-defined exceptions if the method is called requesting an - * authorization decision for a {@link Sid} that was never loaded in this Acl - * . - *

- * @param permission the permission or permissions required (at least one entry - * required) - * @param sids the security identities held by the principal (at least one entry - * required) - * @param administrativeMode if true denotes the query is for administrative - * purposes and no logging or auditing (if supported by the implementation) should be - * undertaken - * @return true if authorization is granted - * @throws NotFoundException MUST be thrown if an implementation cannot make an - * authoritative authorization decision, usually because there is no ACL information - * for this particular permission and/or SID - * @throws UnloadedSidException thrown if the Acl does not have details for - * one or more of the Sids passed as arguments - */ - boolean isGranted(List permission, List sids, boolean administrativeMode) - throws NotFoundException, UnloadedSidException; - - /** - * For efficiency reasons an Acl may be loaded and not contain - * entries for every Sid in the system. If an Acl has been loaded - * and does not represent every Sid, all methods of the Acl can only - * be used within the limited scope of the Sid instances it actually - * represents. - *

- * It is normal to load an Acl for only particular Sids if read-only - * authorization decisions are being made. However, if user interface reporting or - * modification of Acls are desired, an Acl should be loaded with - * all Sids. This method denotes whether or not the specified Sids - * have been loaded or not. - *

- * @param sids one or more security identities the caller is interest in knowing - * whether this Sid supports - * @return true if every passed Sid is represented by this - * Acl instance - */ - boolean isSidLoaded(List sids); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/AclCache.java b/acl/src/main/java/org/springframework/security/acls/model/AclCache.java deleted file mode 100644 index 945b1b1a2ab..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/AclCache.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -import java.io.Serializable; - -import org.springframework.security.acls.jdbc.JdbcAclService; - -/** - * A caching layer for {@link JdbcAclService}. - * - * @author Ben Alex - */ -public interface AclCache { - - void evictFromCache(Serializable pk); - - void evictFromCache(ObjectIdentity objectIdentity); - - MutableAcl getFromCache(ObjectIdentity objectIdentity); - - MutableAcl getFromCache(Serializable pk); - - void putInCache(MutableAcl acl); - - void clearCache(); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/AclDataAccessException.java b/acl/src/main/java/org/springframework/security/acls/model/AclDataAccessException.java deleted file mode 100644 index d37d202a34b..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/AclDataAccessException.java +++ /dev/null @@ -1,46 +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.model; - -/** - * Abstract base class for Acl data operations. - * - * @author Luke Taylor - * @since 3.0 - */ -public abstract class AclDataAccessException extends RuntimeException { - - /** - * Constructs an AclDataAccessException with the specified message and - * root cause. - * @param msg the detail message - * @param cause the root cause - */ - public AclDataAccessException(String msg, Throwable cause) { - super(msg, cause); - } - - /** - * Constructs an AclDataAccessException with the specified message and no - * root cause. - * @param msg the detail message - */ - public AclDataAccessException(String msg) { - super(msg); - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/AclService.java b/acl/src/main/java/org/springframework/security/acls/model/AclService.java deleted file mode 100644 index 2866ec83bd0..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/AclService.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -import java.util.List; -import java.util.Map; - -/** - * Provides retrieval of {@link Acl} instances. - * - * @author Ben Alex - */ -public interface AclService { - - /** - * Locates all object identities that use the specified parent. This is useful for - * administration tools. - * @param parentIdentity to locate children of - * @return the children (or null if none were found) - */ - List findChildren(ObjectIdentity parentIdentity); - - /** - * Same as {@link #readAclsById(List)} except it returns only a single Acl. - *

- * This method should not be called as it does not leverage the underlying - * implementation's potential ability to filter Acl entries based on a - * {@link Sid} parameter. - *

- * @param object to locate an {@link Acl} for - * @return the {@link Acl} for the requested {@link ObjectIdentity} (never - * null) - * @throws NotFoundException if an {@link Acl} was not found for the requested - * {@link ObjectIdentity} - */ - Acl readAclById(ObjectIdentity object) throws NotFoundException; - - /** - * Same as {@link #readAclsById(List, List)} except it returns only a single Acl. - * @param object to locate an {@link Acl} for - * @param sids the security identities for which {@link Acl} information is required - * (may be null to denote all entries) - * @return the {@link Acl} for the requested {@link ObjectIdentity} (never - * null) - * @throws NotFoundException if an {@link Acl} was not found for the requested - * {@link ObjectIdentity} - */ - Acl readAclById(ObjectIdentity object, List sids) throws NotFoundException; - - /** - * Obtains all the Acls that apply for the passed Objects. - *

- * The returned map is keyed on the passed objects, with the values being the - * Acl instances. Any unknown objects will not have a map key. - *

- * @param objects the objects to find {@link Acl} information for - * @return a map with exactly one element for each {@link ObjectIdentity} passed as an - * argument (never null) - * @throws NotFoundException if an {@link Acl} was not found for each requested - * {@link ObjectIdentity} - */ - Map readAclsById(List objects) throws NotFoundException; - - /** - * Obtains all the Acls that apply for the passed Objects, but only - * for the security identifies passed. - *

- * Implementations MAY provide a subset of the ACLs via this method although - * this is NOT a requirement. This is intended to allow performance optimisations - * within implementations. Callers should therefore use this method in preference to - * the alternative overloaded version which does not have performance optimisation - * opportunities. - *

- *

- * The returned map is keyed on the passed objects, with the values being the - * Acl instances. Any unknown objects (or objects for which the interested - * Sids do not have entries) will not have a map key. - *

- * @param objects the objects to find {@link Acl} information for - * @param sids the security identities for which {@link Acl} information is required - * (may be null to denote all entries) - * @return a map with exactly one element for each {@link ObjectIdentity} passed as an - * argument (never null) - * @throws NotFoundException if an {@link Acl} was not found for each requested - * {@link ObjectIdentity} - */ - Map readAclsById(List objects, List sids) throws NotFoundException; - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/AlreadyExistsException.java b/acl/src/main/java/org/springframework/security/acls/model/AlreadyExistsException.java deleted file mode 100644 index 0f82ee6b340..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/AlreadyExistsException.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -/** - * Thrown if an Acl entry already exists for the object. - * - * @author Ben Alex - */ -public class AlreadyExistsException extends AclDataAccessException { - - /** - * Constructs an AlreadyExistsException with the specified message. - * @param msg the detail message - */ - public AlreadyExistsException(String msg) { - super(msg); - } - - /** - * Constructs an AlreadyExistsException with the specified message and - * root cause. - * @param msg the detail message - * @param cause root cause - */ - public AlreadyExistsException(String msg, Throwable cause) { - super(msg, cause); - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/AuditableAccessControlEntry.java b/acl/src/main/java/org/springframework/security/acls/model/AuditableAccessControlEntry.java deleted file mode 100644 index e14a5d15963..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/AuditableAccessControlEntry.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -/** - * Represents an ACE that provides auditing information. - * - * @author Ben Alex - */ -public interface AuditableAccessControlEntry extends AccessControlEntry { - - boolean isAuditFailure(); - - boolean isAuditSuccess(); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/AuditableAcl.java b/acl/src/main/java/org/springframework/security/acls/model/AuditableAcl.java deleted file mode 100644 index 3760d78dc3d..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/AuditableAcl.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -/** - * A mutable ACL that provides audit capabilities. - * - * @author Ben Alex - */ -public interface AuditableAcl extends MutableAcl { - - void updateAuditing(int aceIndex, boolean auditSuccess, boolean auditFailure); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/ChildrenExistException.java b/acl/src/main/java/org/springframework/security/acls/model/ChildrenExistException.java deleted file mode 100644 index d65f1903bba..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/ChildrenExistException.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -/** - * Thrown if an {@link Acl} cannot be deleted because children Acls exist. - * - * @author Ben Alex - */ -public class ChildrenExistException extends AclDataAccessException { - - /** - * Constructs an ChildrenExistException with the specified message. - * @param msg the detail message - */ - public ChildrenExistException(String msg) { - super(msg); - } - - /** - * Constructs an ChildrenExistException with the specified message and - * root cause. - * @param msg the detail message - * @param cause root cause - */ - public ChildrenExistException(String msg, Throwable cause) { - super(msg, cause); - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/MutableAcl.java b/acl/src/main/java/org/springframework/security/acls/model/MutableAcl.java deleted file mode 100644 index 9231a0cc305..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/MutableAcl.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -import java.io.Serializable; - -/** - * A mutable Acl. - *

- * A mutable ACL must ensure that appropriate security checks are performed before - * allowing access to its methods. - * - * @author Ben Alex - */ -public interface MutableAcl extends Acl { - - void deleteAce(int aceIndex) throws NotFoundException; - - /** - * Obtains an identifier that represents this MutableAcl. - * @return the identifier, or null if unsaved - */ - Serializable getId(); - - void insertAce(int atIndexLocation, Permission permission, Sid sid, boolean granting) throws NotFoundException; - - /** - * Changes the present owner to a different owner. - * @param newOwner the new owner (mandatory; cannot be null) - */ - void setOwner(Sid newOwner); - - /** - * Change the value returned by {@link Acl#isEntriesInheriting()}. - * @param entriesInheriting the new value - */ - void setEntriesInheriting(boolean entriesInheriting); - - /** - * Changes the parent of this ACL. - * @param newParent the new parent - */ - void setParent(Acl newParent); - - void updateAce(int aceIndex, Permission permission) throws NotFoundException; - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/MutableAclService.java b/acl/src/main/java/org/springframework/security/acls/model/MutableAclService.java deleted file mode 100644 index cdc229120e2..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/MutableAclService.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -/** - * Provides support for creating and storing Acl instances. - * - * @author Ben Alex - */ -public interface MutableAclService extends AclService { - - /** - * Creates an empty Acl object in the database. It will have no entries. - * The returned object will then be used to add entries. - * @param objectIdentity the object identity to create - * @return an ACL object with its ID set - * @throws AlreadyExistsException if the passed object identity already has a record - */ - MutableAcl createAcl(ObjectIdentity objectIdentity) throws AlreadyExistsException; - - /** - * Removes the specified entry from the database. - * @param objectIdentity the object identity to remove - * @param deleteChildren whether to cascade the delete to children - * @throws ChildrenExistException if the deleteChildren argument was - * false but children exist - */ - void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren) throws ChildrenExistException; - - /** - * Changes an existing Acl in the database. - * @param acl to modify - * @throws NotFoundException if the relevant record could not be found (did you - * remember to use {@link #createAcl(ObjectIdentity)} to create the object, rather - * than creating it with the new keyword?) - */ - MutableAcl updateAcl(MutableAcl acl) throws NotFoundException; - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/NotFoundException.java b/acl/src/main/java/org/springframework/security/acls/model/NotFoundException.java deleted file mode 100644 index 1b0ab639bda..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/NotFoundException.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -/** - * Thrown if an ACL-related object cannot be found. - * - * @author Ben Alex - */ -public class NotFoundException extends AclDataAccessException { - - /** - * Constructs an NotFoundException with the specified message. - * @param msg the detail message - */ - public NotFoundException(String msg) { - super(msg); - } - - /** - * Constructs an NotFoundException with the specified message and root - * cause. - * @param msg the detail message - * @param cause root cause - */ - public NotFoundException(String msg, Throwable cause) { - super(msg, cause); - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/ObjectIdentity.java b/acl/src/main/java/org/springframework/security/acls/model/ObjectIdentity.java deleted file mode 100644 index 995e5140566..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/ObjectIdentity.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -import java.io.Serializable; - -/** - * Represents the identity of an individual domain object instance. - * - *

- * As implementations of ObjectIdentity are used as the key to represent domain - * objects in the ACL subsystem, it is essential that implementations provide methods so - * that object-equality rather than reference-equality can be relied upon reliably. In - * other words, the ACL subsystem can consider two ObjectIdentitys equal if - * identity1.equals(identity2), rather than reference-equality of - * identity1==identity2. - *

- * - * @author Ben Alex - */ -public interface ObjectIdentity extends Serializable { - - /** - * @param obj to be compared - * @return true if the objects are equal, false otherwise - * @see Object#equals(Object) - */ - @Override - boolean equals(Object obj); - - /** - * Obtains the actual identifier. This identifier must not be reused to represent - * other domain objects with the same javaType. - * - *

- * Because ACLs are largely immutable, it is strongly recommended to use a synthetic - * identifier (such as a database sequence number for the primary key). Do not use an - * identifier with business meaning, as that business meaning may change in the future - * such change will cascade to the ACL subsystem data. - *

- * @return the identifier (unique within this type; never null) - */ - Serializable getIdentifier(); - - /** - * Obtains the "type" metadata for the domain object. This will often be a Java type - * name (an interface or a class) – traditionally it is the name of the domain - * object implementation class. - * @return the "type" of the domain object (never null). - */ - String getType(); - - /** - * @return a hash code representation of the ObjectIdentity - * @see Object#hashCode() - */ - @Override - int hashCode(); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/ObjectIdentityGenerator.java b/acl/src/main/java/org/springframework/security/acls/model/ObjectIdentityGenerator.java deleted file mode 100644 index 3bcbf451004..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/ObjectIdentityGenerator.java +++ /dev/null @@ -1,41 +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.model; - -import java.io.Serializable; - -/** - * Strategy which creates an {@link ObjectIdentity} from an object identifier (such as a - * primary key) and type information. - *

- * Differs from {@link ObjectIdentityRetrievalStrategy} in that it is used in situations - * when the actual object instance isn't available. - * - * @author Luke Taylor - * @since 3.0 - */ -public interface ObjectIdentityGenerator { - - /** - * @param id the identifier of the domain object, not null - * @param type the type of the object (often a class name), not null - * @return the identity constructed using the supplied identifier and type - * information. - */ - ObjectIdentity createObjectIdentity(Serializable id, String type); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/ObjectIdentityRetrievalStrategy.java b/acl/src/main/java/org/springframework/security/acls/model/ObjectIdentityRetrievalStrategy.java deleted file mode 100644 index 3838443a5df..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/ObjectIdentityRetrievalStrategy.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -/** - * Strategy interface that provides the ability to determine which {@link ObjectIdentity} - * will be returned for a particular domain object - * - * @author Ben Alex - */ -public interface ObjectIdentityRetrievalStrategy { - - ObjectIdentity getObjectIdentity(Object domainObject); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/OwnershipAcl.java b/acl/src/main/java/org/springframework/security/acls/model/OwnershipAcl.java deleted file mode 100644 index de1e0bbacee..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/OwnershipAcl.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -/** - * A mutable ACL that provides ownership capabilities. - * - *

- * Generally the owner of an ACL is able to call any ACL mutator method, as well as assign - * a new owner. - * - * @author Ben Alex - */ -public interface OwnershipAcl extends MutableAcl { - - @Override - void setOwner(Sid newOwner); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/Permission.java b/acl/src/main/java/org/springframework/security/acls/model/Permission.java deleted file mode 100644 index 99a0d36a76f..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/Permission.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -import java.io.Serializable; - -/** - * Represents a permission granted to a Sid for a given domain object. - * - * @author Ben Alex - */ -public interface Permission extends Serializable { - - char RESERVED_ON = '~'; - - char RESERVED_OFF = '.'; - - String THIRTY_TWO_RESERVED_OFF = "................................"; - - /** - * Returns the bits that represents the permission. - * @return the bits that represent the permission - */ - int getMask(); - - /** - * Returns a 32-character long bit pattern String representing this - * permission. - *

- * Implementations are free to format the pattern as they see fit, although under no - * circumstances may {@link #RESERVED_OFF} or {@link #RESERVED_ON} be used within the - * pattern. An exemption is in the case of {@link #RESERVED_OFF} which is used to - * denote a bit that is off (clear). Implementations may also elect to use - * {@link #RESERVED_ON} internally for computation purposes, although this method may - * not return any String containing {@link #RESERVED_ON}. - *

- * The returned String must be 32 characters in length. - *

- * This method is only used for user interface and logging purposes. It is not used in - * any permission calculations. Therefore, duplication of characters within the output - * is permitted. - * @return a 32-character bit pattern - */ - String getPattern(); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/PermissionGrantingStrategy.java b/acl/src/main/java/org/springframework/security/acls/model/PermissionGrantingStrategy.java deleted file mode 100644 index 9a524e87890..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/PermissionGrantingStrategy.java +++ /dev/null @@ -1,36 +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.model; - -import java.util.List; - -/** - * Allow customization of the logic for determining whether a permission or permissions - * are granted to a particular sid or sids by an {@link Acl}. - * - * @author Luke Taylor - * @since 3.0.2 - */ -public interface PermissionGrantingStrategy { - - /** - * Returns true if the supplied strategy decides that the supplied {@code Acl} grants - * access based on the supplied list of permissions and sids. - */ - boolean isGranted(Acl acl, List permission, List sids, boolean administrativeMode); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/Sid.java b/acl/src/main/java/org/springframework/security/acls/model/Sid.java deleted file mode 100644 index 1e16e8ea947..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/Sid.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -import java.io.Serializable; - -/** - * A security identity recognised by the ACL system. - * - *

- * This interface provides indirection between actual security objects (eg principals, - * roles, groups etc) and what is stored inside an Acl. This is because an - * Acl will not store an entire security object, but only an abstraction of - * it. This interface therefore provides a simple way to compare these abstracted security - * identities with other security identities and actual security objects. - *

- * - * @author Ben Alex - */ -public interface Sid extends Serializable { - - /** - * Refer to the java.lang.Object documentation for the interface - * contract. - * @param obj to be compared - * @return true if the objects are equal, false otherwise - */ - @Override - boolean equals(Object obj); - - /** - * Refer to the java.lang.Object documentation for the interface - * contract. - * @return a hash code representation of this object - */ - @Override - int hashCode(); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/SidRetrievalStrategy.java b/acl/src/main/java/org/springframework/security/acls/model/SidRetrievalStrategy.java deleted file mode 100644 index 42694840d3b..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/SidRetrievalStrategy.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -import java.util.List; - -import org.springframework.security.core.Authentication; - -/** - * Strategy interface that provides an ability to determine the {@link Sid} instances - * applicable for an {@link Authentication}. - * - * @author Ben Alex - */ -public interface SidRetrievalStrategy { - - List getSids(Authentication authentication); - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/UnloadedSidException.java b/acl/src/main/java/org/springframework/security/acls/model/UnloadedSidException.java deleted file mode 100644 index fe208d693b7..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/UnloadedSidException.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.model; - -/** - * Thrown if an {@link Acl} cannot perform an operation because it only loaded a subset of - * Sids and the caller has requested details for an unloaded Sid - * . - * - * @author Ben Alex - */ -public class UnloadedSidException extends AclDataAccessException { - - /** - * Constructs an NotFoundException with the specified message. - * @param msg the detail message - */ - public UnloadedSidException(String msg) { - super(msg); - } - - /** - * Constructs an NotFoundException with the specified message and root - * cause. - * @param msg the detail message - * @param cause root cause - */ - public UnloadedSidException(String msg, Throwable cause) { - super(msg, cause); - } - -} diff --git a/acl/src/main/java/org/springframework/security/acls/model/package-info.java b/acl/src/main/java/org/springframework/security/acls/model/package-info.java deleted file mode 100644 index 36967fc44f4..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/model/package-info.java +++ /dev/null @@ -1,21 +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. - */ - -/** - * Interfaces and shared classes to manage access control lists (ACLs) for domain object - * instances. - */ -package org.springframework.security.acls.model; diff --git a/acl/src/main/java/org/springframework/security/acls/package-info.java b/acl/src/main/java/org/springframework/security/acls/package-info.java deleted file mode 100644 index e137f160817..00000000000 --- a/acl/src/main/java/org/springframework/security/acls/package-info.java +++ /dev/null @@ -1,27 +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. - */ - -/** - * The Spring Security ACL package which implements instance-based security for domain - * objects. - *

- * Consider using the annotation based approach ({@code @PreAuthorize}, - * {@code @PostFilter} annotations) combined with a - * {@link org.springframework.security.acls.AclPermissionEvaluator} in preference to the - * older and more verbose attribute/voter/after-invocation approach from versions before - * Spring Security 3.0. - */ -package org.springframework.security.acls; diff --git a/acl/src/main/resources/createAclSchema.sql b/acl/src/main/resources/createAclSchema.sql deleted file mode 100644 index d7f98d2e206..00000000000 --- a/acl/src/main/resources/createAclSchema.sql +++ /dev/null @@ -1,46 +0,0 @@ --- ACL schema sql used in HSQLDB - --- drop table acl_entry; --- drop table acl_object_identity; --- drop table acl_class; --- drop table acl_sid; - -create table acl_sid( - id bigint generated by default as identity(start with 100) not null primary key, - principal boolean not null, - sid varchar_ignorecase(100) not null, - constraint unique_uk_1 unique(sid,principal) -); - -create table acl_class( - id bigint generated by default as identity(start with 100) not null primary key, - class varchar_ignorecase(100) not null, - constraint unique_uk_2 unique(class) -); - -create table acl_object_identity( - id bigint generated by default as identity(start with 100) not null primary key, - object_id_class bigint not null, - object_id_identity bigint not null, - parent_object bigint, - owner_sid bigint, - entries_inheriting boolean not null, - constraint unique_uk_3 unique(object_id_class,object_id_identity), - constraint foreign_fk_1 foreign key(parent_object)references acl_object_identity(id), - constraint foreign_fk_2 foreign key(object_id_class)references acl_class(id), - constraint foreign_fk_3 foreign key(owner_sid)references acl_sid(id) -); - -create table acl_entry( - id bigint generated by default as identity(start with 100) not null primary key, - acl_object_identity bigint not null, - ace_order int not null, - sid bigint not null, - mask integer not null, - granting boolean not null, - audit_success boolean not null, - audit_failure boolean not null, - constraint unique_uk_4 unique(acl_object_identity,ace_order), - constraint foreign_fk_4 foreign key(acl_object_identity) references acl_object_identity(id), - constraint foreign_fk_5 foreign key(sid) references acl_sid(id) -); diff --git a/acl/src/main/resources/createAclSchemaMySQL.sql b/acl/src/main/resources/createAclSchemaMySQL.sql deleted file mode 100644 index d50120e2fc0..00000000000 --- a/acl/src/main/resources/createAclSchemaMySQL.sql +++ /dev/null @@ -1,46 +0,0 @@ --- ACL Schema SQL for MySQL 5.5+ / MariaDB equivalent - --- drop table acl_entry; --- drop table acl_object_identity; --- drop table acl_class; --- drop table acl_sid; - -CREATE TABLE acl_sid ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - principal BOOLEAN NOT NULL, - sid VARCHAR(100) NOT NULL, - UNIQUE KEY unique_acl_sid (sid, principal) -) ENGINE=InnoDB; - -CREATE TABLE acl_class ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - class VARCHAR(100) NOT NULL, - UNIQUE KEY uk_acl_class (class) -) ENGINE=InnoDB; - -CREATE TABLE acl_object_identity ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - object_id_class BIGINT UNSIGNED NOT NULL, - object_id_identity VARCHAR(36) NOT NULL, - parent_object BIGINT UNSIGNED, - owner_sid BIGINT UNSIGNED, - entries_inheriting BOOLEAN NOT NULL, - UNIQUE KEY uk_acl_object_identity (object_id_class, object_id_identity), - CONSTRAINT fk_acl_object_identity_parent FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id), - CONSTRAINT fk_acl_object_identity_class FOREIGN KEY (object_id_class) REFERENCES acl_class (id), - CONSTRAINT fk_acl_object_identity_owner FOREIGN KEY (owner_sid) REFERENCES acl_sid (id) -) ENGINE=InnoDB; - -CREATE TABLE acl_entry ( - id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - acl_object_identity BIGINT UNSIGNED NOT NULL, - ace_order INTEGER NOT NULL, - sid BIGINT UNSIGNED NOT NULL, - mask INTEGER UNSIGNED NOT NULL, - granting BOOLEAN NOT NULL, - audit_success BOOLEAN NOT NULL, - audit_failure BOOLEAN NOT NULL, - UNIQUE KEY unique_acl_entry (acl_object_identity, ace_order), - CONSTRAINT fk_acl_entry_object FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id), - CONSTRAINT fk_acl_entry_acl FOREIGN KEY (sid) REFERENCES acl_sid (id) -) ENGINE=InnoDB; diff --git a/acl/src/main/resources/createAclSchemaOracle.sql b/acl/src/main/resources/createAclSchemaOracle.sql deleted file mode 100644 index c3fb609bfea..00000000000 --- a/acl/src/main/resources/createAclSchemaOracle.sql +++ /dev/null @@ -1,82 +0,0 @@ --- ACL Schema SQL for Oracle Database 10g+ - --- drop trigger acl_sid_id_trigger; --- drop trigger acl_class_id_trigger; --- drop trigger acl_object_identity_id_trigger; --- drop trigger acl_entry_id_trigger; --- drop sequence acl_sid_sequence; --- drop sequence acl_class_sequence; --- drop sequence acl_object_identity_sequence; --- drop sequence acl_entry_sequence; --- drop table acl_entry; --- drop table acl_object_identity; --- drop table acl_class; --- drop table acl_sid; - -CREATE TABLE acl_sid ( - id NUMBER(38) NOT NULL PRIMARY KEY, - principal NUMBER(1) NOT NULL CHECK (principal in (0, 1)), - sid NVARCHAR2(100) NOT NULL, - CONSTRAINT unique_acl_sid UNIQUE (sid, principal) -); -CREATE SEQUENCE acl_sid_sequence START WITH 1 INCREMENT BY 1 NOMAXVALUE; -CREATE OR REPLACE TRIGGER acl_sid_id_trigger - BEFORE INSERT ON acl_sid - FOR EACH ROW -BEGIN - SELECT acl_sid_sequence.nextval INTO :new.id FROM dual; -END; - -CREATE TABLE acl_class ( - id NUMBER(38) NOT NULL PRIMARY KEY, - class NVARCHAR2(100) NOT NULL, - CONSTRAINT uk_acl_class UNIQUE (class) -); -CREATE SEQUENCE acl_class_sequence START WITH 1 INCREMENT BY 1 NOMAXVALUE; -CREATE OR REPLACE TRIGGER acl_class_id_trigger - BEFORE INSERT ON acl_class - FOR EACH ROW -BEGIN - SELECT acl_class_sequence.nextval INTO :new.id FROM dual; -END; - -CREATE TABLE acl_object_identity ( - id NUMBER(38) NOT NULL PRIMARY KEY, - object_id_class NUMBER(38) NOT NULL, - object_id_identity NVARCHAR2(36) NOT NULL, - parent_object NUMBER(38), - owner_sid NUMBER(38), - entries_inheriting NUMBER(1) NOT NULL CHECK (entries_inheriting in (0, 1)), - CONSTRAINT uk_acl_object_identity UNIQUE (object_id_class, object_id_identity), - CONSTRAINT fk_acl_object_identity_parent FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id), - CONSTRAINT fk_acl_object_identity_class FOREIGN KEY (object_id_class) REFERENCES acl_class (id), - CONSTRAINT fk_acl_object_identity_owner FOREIGN KEY (owner_sid) REFERENCES acl_sid (id) -); -CREATE SEQUENCE acl_object_identity_sequence START WITH 1 INCREMENT BY 1 NOMAXVALUE; -CREATE OR REPLACE TRIGGER acl_object_identity_id_trigger - BEFORE INSERT ON acl_object_identity - FOR EACH ROW -BEGIN - SELECT acl_object_identity_sequence.nextval INTO :new.id FROM dual; -END; - -CREATE TABLE acl_entry ( - id NUMBER(38) NOT NULL PRIMARY KEY, - acl_object_identity NUMBER(38) NOT NULL, - ace_order INTEGER NOT NULL, - sid NUMBER(38) NOT NULL, - mask INTEGER NOT NULL, - granting NUMBER(1) NOT NULL CHECK (granting in (0, 1)), - audit_success NUMBER(1) NOT NULL CHECK (audit_success in (0, 1)), - audit_failure NUMBER(1) NOT NULL CHECK (audit_failure in (0, 1)), - CONSTRAINT unique_acl_entry UNIQUE (acl_object_identity, ace_order), - CONSTRAINT fk_acl_entry_object FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id), - CONSTRAINT fk_acl_entry_acl FOREIGN KEY (sid) REFERENCES acl_sid (id) -); -CREATE SEQUENCE acl_entry_sequence START WITH 1 INCREMENT BY 1 NOMAXVALUE; -CREATE OR REPLACE TRIGGER acl_entry_id_trigger - BEFORE INSERT ON acl_entry - FOR EACH ROW -BEGIN - SELECT acl_entry_sequence.nextval INTO :new.id FROM dual; -END; diff --git a/acl/src/main/resources/createAclSchemaPostgres.sql b/acl/src/main/resources/createAclSchemaPostgres.sql deleted file mode 100644 index ef346ed195b..00000000000 --- a/acl/src/main/resources/createAclSchemaPostgres.sql +++ /dev/null @@ -1,47 +0,0 @@ --- ACL Schema SQL for PostgreSQL - --- drop table acl_entry; --- drop table acl_object_identity; --- drop table acl_class; --- drop table acl_sid; - -create table acl_sid( - id bigserial not null primary key, - principal boolean not null, - sid varchar(100) not null, - constraint unique_uk_1 unique(sid,principal) -); - -create table acl_class( - id bigserial not null primary key, - class varchar(100) not null, - class_id_type varchar(100), - constraint unique_uk_2 unique(class) -); - -create table acl_object_identity( - id bigserial primary key, - object_id_class bigint not null, - object_id_identity varchar(36) not null, - parent_object bigint, - owner_sid bigint, - entries_inheriting boolean not null, - constraint unique_uk_3 unique(object_id_class,object_id_identity), - constraint foreign_fk_1 foreign key(parent_object)references acl_object_identity(id), - constraint foreign_fk_2 foreign key(object_id_class)references acl_class(id), - constraint foreign_fk_3 foreign key(owner_sid)references acl_sid(id) -); - -create table acl_entry( - id bigserial primary key, - acl_object_identity bigint not null, - ace_order int not null, - sid bigint not null, - mask integer not null, - granting boolean not null, - audit_success boolean not null, - audit_failure boolean not null, - constraint unique_uk_4 unique(acl_object_identity,ace_order), - constraint foreign_fk_4 foreign key(acl_object_identity) references acl_object_identity(id), - constraint foreign_fk_5 foreign key(sid) references acl_sid(id) -); diff --git a/acl/src/main/resources/createAclSchemaSqlServer.sql b/acl/src/main/resources/createAclSchemaSqlServer.sql deleted file mode 100644 index b7f5ff23fe9..00000000000 --- a/acl/src/main/resources/createAclSchemaSqlServer.sql +++ /dev/null @@ -1,46 +0,0 @@ --- ACL Schema SQL for Microsoft SQL Server 2008+ - --- drop table acl_entry; --- drop table acl_object_identity; --- drop table acl_class; --- drop table acl_sid; - -CREATE TABLE acl_sid ( - id BIGINT NOT NULL IDENTITY PRIMARY KEY, - principal BIT NOT NULL, - sid VARCHAR(100) NOT NULL, - CONSTRAINT unique_acl_sid UNIQUE (sid, principal) -); - -CREATE TABLE acl_class ( - id BIGINT NOT NULL IDENTITY PRIMARY KEY, - class VARCHAR(100) NOT NULL, - CONSTRAINT uk_acl_class UNIQUE (class) -); - -CREATE TABLE acl_object_identity ( - id BIGINT NOT NULL IDENTITY PRIMARY KEY, - object_id_class BIGINT NOT NULL, - object_id_identity VARCHAR(36) NOT NULL, - parent_object BIGINT, - owner_sid BIGINT, - entries_inheriting BIT NOT NULL, - CONSTRAINT uk_acl_object_identity UNIQUE (object_id_class, object_id_identity), - CONSTRAINT fk_acl_object_identity_parent FOREIGN KEY (parent_object) REFERENCES acl_object_identity (id), - CONSTRAINT fk_acl_object_identity_class FOREIGN KEY (object_id_class) REFERENCES acl_class (id), - CONSTRAINT fk_acl_object_identity_owner FOREIGN KEY (owner_sid) REFERENCES acl_sid (id) -); - -CREATE TABLE acl_entry ( - id BIGINT NOT NULL IDENTITY PRIMARY KEY, - acl_object_identity BIGINT NOT NULL, - ace_order INTEGER NOT NULL, - sid BIGINT NOT NULL, - mask INTEGER NOT NULL, - granting BIT NOT NULL, - audit_success BIT NOT NULL, - audit_failure BIT NOT NULL, - CONSTRAINT unique_acl_entry UNIQUE (acl_object_identity, ace_order), - CONSTRAINT fk_acl_entry_object FOREIGN KEY (acl_object_identity) REFERENCES acl_object_identity (id), - CONSTRAINT fk_acl_entry_acl FOREIGN KEY (sid) REFERENCES acl_sid (id) -); diff --git a/acl/src/main/resources/createAclSchemaWithAclClassIdType.sql b/acl/src/main/resources/createAclSchemaWithAclClassIdType.sql deleted file mode 100644 index 40594f1d148..00000000000 --- a/acl/src/main/resources/createAclSchemaWithAclClassIdType.sql +++ /dev/null @@ -1,47 +0,0 @@ --- ACL schema sql used in HSQLDB - --- drop table acl_entry; --- drop table acl_object_identity; --- drop table acl_class; --- drop table acl_sid; - -create table acl_sid( - id bigint generated by default as identity(start with 100) not null primary key, - principal boolean not null, - sid varchar_ignorecase(100) not null, - constraint unique_uk_1 unique(sid,principal) -); - -create table acl_class( - id bigint generated by default as identity(start with 100) not null primary key, - class varchar_ignorecase(100) not null, - class_id_type varchar_ignorecase(100), - constraint unique_uk_2 unique(class) -); - -create table acl_object_identity( - id bigint generated by default as identity(start with 100) not null primary key, - object_id_class bigint not null, - object_id_identity varchar_ignorecase(36) not null, - parent_object bigint, - owner_sid bigint, - entries_inheriting boolean not null, - constraint unique_uk_3 unique(object_id_class,object_id_identity), - constraint foreign_fk_1 foreign key(parent_object)references acl_object_identity(id), - constraint foreign_fk_2 foreign key(object_id_class)references acl_class(id), - constraint foreign_fk_3 foreign key(owner_sid)references acl_sid(id) -); - -create table acl_entry( - id bigint generated by default as identity(start with 100) not null primary key, - acl_object_identity bigint not null, - ace_order int not null, - sid bigint not null, - mask integer not null, - granting boolean not null, - audit_success boolean not null, - audit_failure boolean not null, - constraint unique_uk_4 unique(acl_object_identity,ace_order), - constraint foreign_fk_4 foreign key(acl_object_identity) references acl_object_identity(id), - constraint foreign_fk_5 foreign key(sid) references acl_sid(id) -); diff --git a/acl/src/main/resources/select.sql b/acl/src/main/resources/select.sql deleted file mode 100644 index ad759c7c7cb..00000000000 --- a/acl/src/main/resources/select.sql +++ /dev/null @@ -1,39 +0,0 @@ --- Not required. Just shows the sort of queries being sent to DB. - - -select acl_object_identity.object_id_identity, - acl_entry.ace_order, - acl_object_identity.id as acl_id, - acl_object_identity.parent_object, - acl_object_identity, - entries_inheriting, - acl_entry.id as ace_id, - acl_entry.mask, - acl_entry.granting, - acl_entry.audit_success, - acl_entry.audit_failure, - acl_sid.principal as ace_principal, - acl_sid.sid as ace_sid, - acli_sid.principal as acl_principal, - acli_sid.sid as acl_sid, - acl_class.class - -from acl_object_identity, - acl_sid acli_sid, - acl_class - -left join acl_entry on acl_object_identity.id = acl_entry.acl_object_identity -left join acl_sid on acl_entry.sid = acl_sid.id - -where - acli_sid.id = acl_object_identity.owner_sid - -and acl_class.id = acl_object_identity.object_id_class - -and ( - - (acl_object_identity.object_id_identity = 1 and acl_class.class = 'sample.contact.contact') -or - (acl_object_identity.object_id_identity = 2000 and acl_class.class = 'sample.contact.contact') - -) order by acl_object_identity.object_id_identity asc, acl_entry.ace_order asc diff --git a/acl/src/test/java/org/springframework/security/acls/AclFormattingUtilsTests.java b/acl/src/test/java/org/springframework/security/acls/AclFormattingUtilsTests.java deleted file mode 100644 index 21cd56fe789..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/AclFormattingUtilsTests.java +++ /dev/null @@ -1,92 +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; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.acls.domain.AclFormattingUtils; -import org.springframework.security.acls.model.Permission; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.assertThatNoException; - -/** - * Tests for {@link AclFormattingUtils}. - * - * @author Andrei Stefan - */ -public class AclFormattingUtilsTests { - - @Test - public final void testDemergePatternsParametersConstraints() { - assertThatIllegalArgumentException().isThrownBy(() -> AclFormattingUtils.demergePatterns(null, "SOME STRING")); - assertThatIllegalArgumentException().isThrownBy(() -> AclFormattingUtils.demergePatterns("SOME STRING", null)); - assertThatIllegalArgumentException() - .isThrownBy(() -> AclFormattingUtils.demergePatterns("SOME STRING", "LONGER SOME STRING")); - assertThatNoException().isThrownBy(() -> AclFormattingUtils.demergePatterns("SOME STRING", "SAME LENGTH")); - } - - @Test - public final void testDemergePatterns() { - String original = "...........................A...R"; - String removeBits = "...............................R"; - assertThat(AclFormattingUtils.demergePatterns(original, removeBits)) - .isEqualTo("...........................A...."); - assertThat(AclFormattingUtils.demergePatterns("ABCDEF", "......")).isEqualTo("ABCDEF"); - assertThat(AclFormattingUtils.demergePatterns("ABCDEF", "GHIJKL")).isEqualTo("......"); - } - - @Test - public final void testMergePatternsParametersConstraints() { - assertThatIllegalArgumentException().isThrownBy(() -> AclFormattingUtils.mergePatterns(null, "SOME STRING")); - assertThatIllegalArgumentException().isThrownBy(() -> AclFormattingUtils.mergePatterns("SOME STRING", null)); - assertThatIllegalArgumentException() - .isThrownBy(() -> AclFormattingUtils.mergePatterns("SOME STRING", "LONGER SOME STRING")); - assertThatNoException().isThrownBy(() -> AclFormattingUtils.mergePatterns("SOME STRING", "SAME LENGTH")); - } - - @Test - public final void testMergePatterns() { - String original = "...............................R"; - String extraBits = "...........................A...."; - assertThat(AclFormattingUtils.mergePatterns(original, extraBits)).isEqualTo("...........................A...R"); - assertThat(AclFormattingUtils.mergePatterns("ABCDEF", "......")).isEqualTo("ABCDEF"); - assertThat(AclFormattingUtils.mergePatterns("ABCDEF", "GHIJKL")).isEqualTo("GHIJKL"); - } - - @Test - public final void testBinaryPrints() { - assertThat(AclFormattingUtils.printBinary(15)).isEqualTo("............................****"); - assertThatIllegalArgumentException() - .isThrownBy(() -> AclFormattingUtils.printBinary(15, Permission.RESERVED_ON)); - assertThatIllegalArgumentException() - .isThrownBy(() -> AclFormattingUtils.printBinary(15, Permission.RESERVED_OFF)); - assertThat(AclFormattingUtils.printBinary(15, 'x')).isEqualTo("............................xxxx"); - } - - @Test - public void testPrintBinaryNegative() { - assertThat(AclFormattingUtils.printBinary(0x80000000)).isEqualTo("*..............................."); - } - - @Test - public void testPrintBinaryMinusOne() { - assertThat(AclFormattingUtils.printBinary(0xffffffff)).isEqualTo("********************************"); - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/AclPermissionCacheOptimizerTests.java b/acl/src/test/java/org/springframework/security/acls/AclPermissionCacheOptimizerTests.java deleted file mode 100644 index 8d0f899da85..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/AclPermissionCacheOptimizerTests.java +++ /dev/null @@ -1,74 +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; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.acls.domain.ObjectIdentityImpl; -import org.springframework.security.acls.model.AclService; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; -import org.springframework.security.acls.model.SidRetrievalStrategy; -import org.springframework.security.core.Authentication; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -/** - * @author Luke Taylor - */ -@SuppressWarnings({ "unchecked" }) -public class AclPermissionCacheOptimizerTests { - - @Test - public void eagerlyLoadsRequiredAcls() { - AclService service = mock(AclService.class); - AclPermissionCacheOptimizer pco = new AclPermissionCacheOptimizer(service); - ObjectIdentityRetrievalStrategy oidStrat = mock(ObjectIdentityRetrievalStrategy.class); - SidRetrievalStrategy sidStrat = mock(SidRetrievalStrategy.class); - pco.setObjectIdentityRetrievalStrategy(oidStrat); - pco.setSidRetrievalStrategy(sidStrat); - Object[] dos = { new Object(), null, new Object() }; - ObjectIdentity[] oids = { new ObjectIdentityImpl("A", "1"), new ObjectIdentityImpl("A", "2") }; - given(oidStrat.getObjectIdentity(dos[0])).willReturn(oids[0]); - given(oidStrat.getObjectIdentity(dos[2])).willReturn(oids[1]); - pco.cachePermissionsFor(mock(Authentication.class), Arrays.asList(dos)); - // AclService should be invoked with the list of required Oids - verify(service).readAclsById(eq(Arrays.asList(oids)), any(List.class)); - } - - @Test - public void ignoresEmptyCollection() { - AclService service = mock(AclService.class); - AclPermissionCacheOptimizer pco = new AclPermissionCacheOptimizer(service); - ObjectIdentityRetrievalStrategy oids = mock(ObjectIdentityRetrievalStrategy.class); - SidRetrievalStrategy sids = mock(SidRetrievalStrategy.class); - pco.setObjectIdentityRetrievalStrategy(oids); - pco.setSidRetrievalStrategy(sids); - pco.cachePermissionsFor(mock(Authentication.class), Collections.emptyList()); - verifyNoMoreInteractions(service, sids, oids); - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/AclPermissionEvaluatorTests.java b/acl/src/test/java/org/springframework/security/acls/AclPermissionEvaluatorTests.java deleted file mode 100644 index cb328006bf6..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/AclPermissionEvaluatorTests.java +++ /dev/null @@ -1,76 +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; - -import java.util.Locale; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.acls.model.Acl; -import org.springframework.security.acls.model.AclService; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; -import org.springframework.security.acls.model.SidRetrievalStrategy; -import org.springframework.security.core.Authentication; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyList; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * @author Luke Taylor - * @since 3.0 - */ -public class AclPermissionEvaluatorTests { - - @Test - public void hasPermissionReturnsTrueIfAclGrantsPermission() { - AclService service = mock(AclService.class); - AclPermissionEvaluator pe = new AclPermissionEvaluator(service); - ObjectIdentity oid = mock(ObjectIdentity.class); - ObjectIdentityRetrievalStrategy oidStrategy = mock(ObjectIdentityRetrievalStrategy.class); - given(oidStrategy.getObjectIdentity(any(Object.class))).willReturn(oid); - pe.setObjectIdentityRetrievalStrategy(oidStrategy); - pe.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class)); - Acl acl = mock(Acl.class); - given(service.readAclById(any(ObjectIdentity.class), anyList())).willReturn(acl); - given(acl.isGranted(anyList(), anyList(), eq(false))).willReturn(true); - assertThat(pe.hasPermission(mock(Authentication.class), new Object(), "READ")).isTrue(); - } - - @Test - public void resolvePermissionNonEnglishLocale() { - Locale systemLocale = Locale.getDefault(); - Locale.setDefault(new Locale("tr")); - AclService service = mock(AclService.class); - AclPermissionEvaluator pe = new AclPermissionEvaluator(service); - ObjectIdentity oid = mock(ObjectIdentity.class); - ObjectIdentityRetrievalStrategy oidStrategy = mock(ObjectIdentityRetrievalStrategy.class); - given(oidStrategy.getObjectIdentity(any(Object.class))).willReturn(oid); - pe.setObjectIdentityRetrievalStrategy(oidStrategy); - pe.setSidRetrievalStrategy(mock(SidRetrievalStrategy.class)); - Acl acl = mock(Acl.class); - given(service.readAclById(any(ObjectIdentity.class), anyList())).willReturn(acl); - given(acl.isGranted(anyList(), anyList(), eq(false))).willReturn(true); - assertThat(pe.hasPermission(mock(Authentication.class), new Object(), "write")).isTrue(); - Locale.setDefault(systemLocale); - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/TargetObject.java b/acl/src/test/java/org/springframework/security/acls/TargetObject.java deleted file mode 100644 index f096298fbdc..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/TargetObject.java +++ /dev/null @@ -1,26 +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; - -/** - * Dummy domain object class - * - * @author Luke Taylor - */ -public final class TargetObject { - -} diff --git a/acl/src/test/java/org/springframework/security/acls/TargetObjectWithUUID.java b/acl/src/test/java/org/springframework/security/acls/TargetObjectWithUUID.java deleted file mode 100644 index f2d5acd2345..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/TargetObjectWithUUID.java +++ /dev/null @@ -1,38 +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; - -import java.util.UUID; - -/** - * Dummy domain object class with a {@link UUID} for the Id. - * - * @author Luke Taylor - */ -public final class TargetObjectWithUUID { - - private UUID id; - - public UUID getId() { - return this.id; - } - - public void setId(UUID id) { - this.id = id; - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/domain/AccessControlImplEntryTests.java b/acl/src/test/java/org/springframework/security/acls/domain/AccessControlImplEntryTests.java deleted file mode 100644 index 45edad8fdba..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/domain/AccessControlImplEntryTests.java +++ /dev/null @@ -1,96 +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.domain; - -import org.junit.jupiter.api.Test; - -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; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link AccessControlEntryImpl}. - * - * @author Andrei Stefan - */ -public class AccessControlImplEntryTests { - - @Test - public void testConstructorRequiredFields() { - // Check Acl field is present - assertThatIllegalArgumentException().isThrownBy(() -> new AccessControlEntryImpl(null, null, - new PrincipalSid("johndoe"), BasePermission.ADMINISTRATION, true, true, true)); - // Check Sid field is present - assertThatIllegalArgumentException().isThrownBy(() -> new AccessControlEntryImpl(null, mock(Acl.class), null, - BasePermission.ADMINISTRATION, true, true, true)); - // Check Permission field is present - assertThatIllegalArgumentException().isThrownBy(() -> new AccessControlEntryImpl(null, mock(Acl.class), - new PrincipalSid("johndoe"), null, true, true, true)); - } - - @Test - public void testAccessControlEntryImplGetters() { - Acl mockAcl = mock(Acl.class); - Sid sid = new PrincipalSid("johndoe"); - // Create a sample entry - AccessControlEntry ace = new AccessControlEntryImpl(1L, mockAcl, sid, BasePermission.ADMINISTRATION, true, true, - true); - // and check every get() method - assertThat(ace.getId()).isEqualTo(1L); - assertThat(ace.getAcl()).isEqualTo(mockAcl); - assertThat(ace.getSid()).isEqualTo(sid); - assertThat(ace.isGranting()).isTrue(); - assertThat(ace.getPermission()).isEqualTo(BasePermission.ADMINISTRATION); - assertThat(((AuditableAccessControlEntry) ace).isAuditFailure()).isTrue(); - assertThat(((AuditableAccessControlEntry) ace).isAuditSuccess()).isTrue(); - } - - @Test - public void testEquals() { - final Acl mockAcl = mock(Acl.class); - final ObjectIdentity oid = mock(ObjectIdentity.class); - given(mockAcl.getObjectIdentity()).willReturn(oid); - Sid sid = new PrincipalSid("johndoe"); - AccessControlEntry ace = new AccessControlEntryImpl(1L, mockAcl, sid, BasePermission.ADMINISTRATION, true, true, - true); - assertThat(ace).isNotNull(); - assertThat(ace).isNotEqualTo(100L); - assertThat(ace).isEqualTo(ace); - assertThat(ace) - .isEqualTo(new AccessControlEntryImpl(1L, mockAcl, sid, BasePermission.ADMINISTRATION, true, true, true)); - assertThat(ace).isNotEqualTo( - new AccessControlEntryImpl(2L, mockAcl, sid, BasePermission.ADMINISTRATION, true, true, true)); - assertThat(ace).isNotEqualTo(new AccessControlEntryImpl(1L, mockAcl, new PrincipalSid("scott"), - BasePermission.ADMINISTRATION, true, true, true)); - assertThat(ace) - .isNotEqualTo(new AccessControlEntryImpl(1L, mockAcl, sid, BasePermission.WRITE, true, true, true)); - assertThat(ace).isNotEqualTo( - new AccessControlEntryImpl(1L, mockAcl, sid, BasePermission.ADMINISTRATION, false, true, true)); - assertThat(ace).isNotEqualTo( - new AccessControlEntryImpl(1L, mockAcl, sid, BasePermission.ADMINISTRATION, true, false, true)); - assertThat(ace).isNotEqualTo( - new AccessControlEntryImpl(1L, mockAcl, sid, BasePermission.ADMINISTRATION, true, true, false)); - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImplTests.java b/acl/src/test/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImplTests.java deleted file mode 100644 index bfb6ac10832..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/domain/AclAuthorizationStrategyImplTests.java +++ /dev/null @@ -1,120 +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.domain; - -import java.util.Arrays; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; -import org.springframework.security.acls.model.Acl; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.core.context.SecurityContextImpl; - -import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.verify; - -/** - * @author Rob Winch - * - */ -@ExtendWith(MockitoExtension.class) -public class AclAuthorizationStrategyImplTests { - - SecurityContext context; - - @Mock - Acl acl; - - @Mock - SecurityContextHolderStrategy securityContextHolderStrategy; - - GrantedAuthority authority; - - AclAuthorizationStrategyImpl strategy; - - @BeforeEach - public void setup() { - this.authority = new SimpleGrantedAuthority("ROLE_AUTH"); - TestingAuthenticationToken authentication = new TestingAuthenticationToken("foo", "bar", - Arrays.asList(this.authority)); - authentication.setAuthenticated(true); - this.context = new SecurityContextImpl(authentication); - SecurityContextHolder.setContext(this.context); - } - - @AfterEach - public void cleanup() { - SecurityContextHolder.clearContext(); - } - - // gh-4085 - @Test - public void securityCheckWhenCustomAuthorityThenNameIsUsed() { - this.strategy = new AclAuthorizationStrategyImpl(new CustomAuthority()); - this.strategy.securityCheck(this.acl, AclAuthorizationStrategy.CHANGE_GENERAL); - } - - // gh-9425 - @Test - public void securityCheckWhenAclOwnedByGrantedAuthority() { - given(this.acl.getOwner()).willReturn(new GrantedAuthoritySid("ROLE_AUTH")); - this.strategy = new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_SYSTEM_ADMIN")); - this.strategy.securityCheck(this.acl, AclAuthorizationStrategy.CHANGE_GENERAL); - } - - @Test - public void securityCheckWhenRoleReachableByHierarchyThenAuthorized() { - given(this.acl.getOwner()).willReturn(new GrantedAuthoritySid("ROLE_AUTH_B")); - this.strategy = new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_SYSTEM_ADMIN")); - this.strategy.setRoleHierarchy(RoleHierarchyImpl.fromHierarchy("ROLE_AUTH > ROLE_AUTH_B")); - assertThatNoException() - .isThrownBy(() -> this.strategy.securityCheck(this.acl, AclAuthorizationStrategy.CHANGE_GENERAL)); - } - - @Test - public void securityCheckWhenCustomSecurityContextHolderStrategyThenUses() { - given(this.securityContextHolderStrategy.getContext()).willReturn(this.context); - given(this.acl.getOwner()).willReturn(new GrantedAuthoritySid("ROLE_AUTH")); - this.strategy = new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_SYSTEM_ADMIN")); - this.strategy.setSecurityContextHolderStrategy(this.securityContextHolderStrategy); - this.strategy.securityCheck(this.acl, AclAuthorizationStrategy.CHANGE_GENERAL); - verify(this.securityContextHolderStrategy).getContext(); - } - - @SuppressWarnings("serial") - class CustomAuthority implements GrantedAuthority { - - @Override - public String getAuthority() { - return AclAuthorizationStrategyImplTests.this.authority.getAuthority(); - } - - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/domain/AclImplTests.java b/acl/src/test/java/org/springframework/security/acls/domain/AclImplTests.java deleted file mode 100644 index c0d7b2d1386..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/domain/AclImplTests.java +++ /dev/null @@ -1,582 +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.domain; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.acls.model.AccessControlEntry; -import org.springframework.security.acls.model.Acl; -import org.springframework.security.acls.model.AlreadyExistsException; -import org.springframework.security.acls.model.AuditableAccessControlEntry; -import org.springframework.security.acls.model.AuditableAcl; -import org.springframework.security.acls.model.ChildrenExistException; -import org.springframework.security.acls.model.MutableAcl; -import org.springframework.security.acls.model.MutableAclService; -import org.springframework.security.acls.model.NotFoundException; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.Permission; -import org.springframework.security.acls.model.PermissionGrantingStrategy; -import org.springframework.security.acls.model.Sid; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.util.FieldUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link AclImpl}. - * - * @author Andrei Stefan - */ -public class AclImplTests { - - private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject"; - - private static final List READ = Arrays.asList(BasePermission.READ); - - private static final List WRITE = Arrays.asList(BasePermission.WRITE); - - private static final List CREATE = Arrays.asList(BasePermission.CREATE); - - private static final List DELETE = Arrays.asList(BasePermission.DELETE); - - private static final List SCOTT = Arrays.asList((Sid) new PrincipalSid("scott")); - - private static final List BEN = Arrays.asList((Sid) new PrincipalSid("ben")); - - Authentication auth = new TestingAuthenticationToken("joe", "ignored", "ROLE_ADMINISTRATOR"); - - AclAuthorizationStrategy authzStrategy; - - PermissionGrantingStrategy pgs; - - AuditLogger mockAuditLogger; - - ObjectIdentity objectIdentity = new ObjectIdentityImpl(TARGET_CLASS, 100); - - private DefaultPermissionFactory permissionFactory; - - @BeforeEach - public void setUp() { - SecurityContextHolder.getContext().setAuthentication(this.auth); - this.authzStrategy = mock(AclAuthorizationStrategy.class); - this.mockAuditLogger = mock(AuditLogger.class); - this.pgs = new DefaultPermissionGrantingStrategy(this.mockAuditLogger); - this.auth.setAuthenticated(true); - this.permissionFactory = new DefaultPermissionFactory(); - } - - @AfterEach - public void tearDown() { - SecurityContextHolder.clearContext(); - } - - @Test - public void constructorsRejectNullObjectIdentity() { - assertThatIllegalArgumentException().isThrownBy( - () -> new AclImpl(null, 1, this.authzStrategy, this.pgs, null, null, true, new PrincipalSid("joe"))); - assertThatIllegalArgumentException() - .isThrownBy(() -> new AclImpl(null, 1, this.authzStrategy, this.mockAuditLogger)); - } - - @Test - public void constructorsRejectNullId() { - assertThatIllegalArgumentException().isThrownBy(() -> new AclImpl(this.objectIdentity, null, this.authzStrategy, - this.pgs, null, null, true, new PrincipalSid("joe"))); - assertThatIllegalArgumentException() - .isThrownBy(() -> new AclImpl(this.objectIdentity, null, this.authzStrategy, this.mockAuditLogger)); - } - - @Test - public void constructorsRejectNullAclAuthzStrategy() { - assertThatIllegalArgumentException().isThrownBy(() -> new AclImpl(this.objectIdentity, 1, null, - new DefaultPermissionGrantingStrategy(this.mockAuditLogger), null, null, true, - new PrincipalSid("joe"))); - assertThatIllegalArgumentException() - .isThrownBy(() -> new AclImpl(this.objectIdentity, 1, null, this.mockAuditLogger)); - } - - @Test - public void insertAceRejectsNullParameters() { - MutableAcl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, - new PrincipalSid("joe")); - assertThatIllegalArgumentException() - .isThrownBy(() -> acl.insertAce(0, null, new GrantedAuthoritySid("ROLE_IGNORED"), true)); - assertThatIllegalArgumentException().isThrownBy(() -> acl.insertAce(0, BasePermission.READ, null, true)); - } - - @Test - public void insertAceAddsElementAtCorrectIndex() { - MutableAcl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, - new PrincipalSid("joe")); - MockAclService service = new MockAclService(); - // Insert one permission - acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST1"), true); - service.updateAcl(acl); - // Check it was successfully added - assertThat(acl.getEntries()).hasSize(1); - assertThat(acl).isEqualTo(acl.getEntries().get(0).getAcl()); - assertThat(BasePermission.READ).isEqualTo(acl.getEntries().get(0).getPermission()); - assertThat(acl.getEntries().get(0).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST1")); - // Add a second permission - acl.insertAce(1, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST2"), true); - service.updateAcl(acl); - // Check it was added on the last position - assertThat(acl.getEntries()).hasSize(2); - assertThat(acl).isEqualTo(acl.getEntries().get(1).getAcl()); - assertThat(BasePermission.READ).isEqualTo(acl.getEntries().get(1).getPermission()); - assertThat(acl.getEntries().get(1).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST2")); - // Add a third permission, after the first one - acl.insertAce(1, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_TEST3"), false); - service.updateAcl(acl); - assertThat(acl.getEntries()).hasSize(3); - // Check the third entry was added between the two existent ones - assertThat(BasePermission.READ).isEqualTo(acl.getEntries().get(0).getPermission()); - assertThat(acl.getEntries().get(0).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST1")); - assertThat(BasePermission.WRITE).isEqualTo(acl.getEntries().get(1).getPermission()); - assertThat(acl.getEntries().get(1).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST3")); - assertThat(BasePermission.READ).isEqualTo(acl.getEntries().get(2).getPermission()); - assertThat(acl.getEntries().get(2).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST2")); - } - - @Test - public void insertAceFailsForNonExistentElement() { - MutableAcl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, - new PrincipalSid("joe")); - MockAclService service = new MockAclService(); - // Insert one permission - acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST1"), true); - service.updateAcl(acl); - assertThatExceptionOfType(NotFoundException.class) - .isThrownBy(() -> acl.insertAce(55, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST2"), true)); - } - - @Test - public void deleteAceKeepsInitialOrdering() { - MutableAcl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, - new PrincipalSid("joe")); - MockAclService service = new MockAclService(); - // Add several permissions - acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST1"), true); - acl.insertAce(1, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST2"), true); - acl.insertAce(2, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST3"), true); - service.updateAcl(acl); - // Delete first permission and check the order of the remaining permissions is - // kept - acl.deleteAce(0); - assertThat(acl.getEntries()).hasSize(2); - assertThat(acl.getEntries().get(0).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST2")); - assertThat(acl.getEntries().get(1).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST3")); - // Add one more permission and remove the permission in the middle - acl.insertAce(2, BasePermission.READ, new GrantedAuthoritySid("ROLE_TEST4"), true); - service.updateAcl(acl); - acl.deleteAce(1); - assertThat(acl.getEntries()).hasSize(2); - assertThat(acl.getEntries().get(0).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST2")); - assertThat(acl.getEntries().get(1).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST4")); - // Remove remaining permissions - acl.deleteAce(1); - acl.deleteAce(0); - assertThat(acl.getEntries()).isEmpty(); - } - - @Test - public void deleteAceFailsForNonExistentElement() { - AclAuthorizationStrategyImpl strategy = new AclAuthorizationStrategyImpl( - new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"), - new SimpleGrantedAuthority("ROLE_GENERAL")); - MutableAcl acl = new AclImpl(this.objectIdentity, (1), strategy, this.pgs, null, null, true, - new PrincipalSid("joe")); - assertThatExceptionOfType(NotFoundException.class).isThrownBy(() -> acl.deleteAce(99)); - } - - @Test - public void isGrantingRejectsEmptyParameters() { - MutableAcl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, - new PrincipalSid("joe")); - Sid ben = new PrincipalSid("ben"); - assertThatIllegalArgumentException() - .isThrownBy(() -> acl.isGranted(new ArrayList<>(0), Arrays.asList(ben), false)); - assertThatIllegalArgumentException().isThrownBy(() -> acl.isGranted(READ, new ArrayList<>(0), false)); - } - - @Test - public void isGrantingGrantsAccessForAclWithNoParent() { - Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_GENERAL", "ROLE_GUEST"); - auth.setAuthenticated(true); - SecurityContextHolder.getContext().setAuthentication(auth); - ObjectIdentity rootOid = new ObjectIdentityImpl(TARGET_CLASS, 100); - // Create an ACL which owner is not the authenticated principal - MutableAcl rootAcl = new AclImpl(rootOid, 1, this.authzStrategy, this.pgs, null, null, false, - new PrincipalSid("joe")); - // Grant some permissions - rootAcl.insertAce(0, BasePermission.READ, new PrincipalSid("ben"), false); - rootAcl.insertAce(1, BasePermission.WRITE, new PrincipalSid("scott"), true); - rootAcl.insertAce(2, BasePermission.WRITE, new PrincipalSid("rod"), false); - rootAcl.insertAce(3, BasePermission.WRITE, new GrantedAuthoritySid("WRITE_ACCESS_ROLE"), true); - // Check permissions granting - List permissions = Arrays.asList(BasePermission.READ, BasePermission.CREATE); - List sids = Arrays.asList(new PrincipalSid("ben"), new GrantedAuthoritySid("ROLE_GUEST")); - assertThat(rootAcl.isGranted(permissions, sids, false)).isFalse(); - assertThatExceptionOfType(NotFoundException.class) - .isThrownBy(() -> rootAcl.isGranted(permissions, SCOTT, false)); - assertThat(rootAcl.isGranted(WRITE, SCOTT, false)).isTrue(); - assertThat(rootAcl.isGranted(WRITE, - Arrays.asList(new PrincipalSid("rod"), new GrantedAuthoritySid("WRITE_ACCESS_ROLE")), false)) - .isFalse(); - assertThat(rootAcl.isGranted(WRITE, - Arrays.asList(new GrantedAuthoritySid("WRITE_ACCESS_ROLE"), new PrincipalSid("rod")), false)) - .isTrue(); - // Change the type of the Sid and check the granting process - assertThatExceptionOfType(NotFoundException.class).isThrownBy(() -> rootAcl.isGranted(WRITE, - Arrays.asList(new GrantedAuthoritySid("rod"), new PrincipalSid("WRITE_ACCESS_ROLE")), false)); - } - - @Test - public void isGrantingGrantsAccessForInheritableAcls() { - Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_GENERAL"); - auth.setAuthenticated(true); - SecurityContextHolder.getContext().setAuthentication(auth); - ObjectIdentity grandParentOid = new ObjectIdentityImpl(TARGET_CLASS, 100); - ObjectIdentity parentOid1 = new ObjectIdentityImpl(TARGET_CLASS, 101); - ObjectIdentity parentOid2 = new ObjectIdentityImpl(TARGET_CLASS, 102); - ObjectIdentity childOid1 = new ObjectIdentityImpl(TARGET_CLASS, 103); - ObjectIdentity childOid2 = new ObjectIdentityImpl(TARGET_CLASS, 104); - // Create ACLs - PrincipalSid joe = new PrincipalSid("joe"); - MutableAcl grandParentAcl = new AclImpl(grandParentOid, 1, this.authzStrategy, this.pgs, null, null, false, - joe); - MutableAcl parentAcl1 = new AclImpl(parentOid1, 2, this.authzStrategy, this.pgs, null, null, true, joe); - MutableAcl parentAcl2 = new AclImpl(parentOid2, 3, this.authzStrategy, this.pgs, null, null, true, joe); - MutableAcl childAcl1 = new AclImpl(childOid1, 4, this.authzStrategy, this.pgs, null, null, true, joe); - MutableAcl childAcl2 = new AclImpl(childOid2, 4, this.authzStrategy, this.pgs, null, null, false, joe); - // Create hierarchies - childAcl2.setParent(childAcl1); - childAcl1.setParent(parentAcl1); - parentAcl2.setParent(grandParentAcl); - parentAcl1.setParent(grandParentAcl); - // Add some permissions - grandParentAcl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"), true); - grandParentAcl.insertAce(1, BasePermission.WRITE, new PrincipalSid("ben"), true); - grandParentAcl.insertAce(2, BasePermission.DELETE, new PrincipalSid("ben"), false); - grandParentAcl.insertAce(3, BasePermission.DELETE, new PrincipalSid("scott"), true); - parentAcl1.insertAce(0, BasePermission.READ, new PrincipalSid("scott"), true); - parentAcl1.insertAce(1, BasePermission.DELETE, new PrincipalSid("scott"), false); - parentAcl2.insertAce(0, BasePermission.CREATE, new PrincipalSid("ben"), true); - childAcl1.insertAce(0, BasePermission.CREATE, new PrincipalSid("scott"), true); - // Check granting process for parent1 - assertThat(parentAcl1.isGranted(READ, SCOTT, false)).isTrue(); - assertThat(parentAcl1.isGranted(READ, Arrays.asList((Sid) new GrantedAuthoritySid("ROLE_USER_READ")), false)) - .isTrue(); - assertThat(parentAcl1.isGranted(WRITE, BEN, false)).isTrue(); - assertThat(parentAcl1.isGranted(DELETE, BEN, false)).isFalse(); - assertThat(parentAcl1.isGranted(DELETE, SCOTT, false)).isFalse(); - // Check granting process for parent2 - assertThat(parentAcl2.isGranted(CREATE, BEN, false)).isTrue(); - assertThat(parentAcl2.isGranted(WRITE, BEN, false)).isTrue(); - assertThat(parentAcl2.isGranted(DELETE, BEN, false)).isFalse(); - // Check granting process for child1 - assertThat(childAcl1.isGranted(CREATE, SCOTT, false)).isTrue(); - assertThat(childAcl1.isGranted(READ, Arrays.asList((Sid) new GrantedAuthoritySid("ROLE_USER_READ")), false)) - .isTrue(); - assertThat(childAcl1.isGranted(DELETE, BEN, false)).isFalse(); - // Check granting process for child2 (doesn't inherit the permissions from its - // parent) - assertThatExceptionOfType(NotFoundException.class).isThrownBy(() -> childAcl2.isGranted(CREATE, SCOTT, false)); - assertThatExceptionOfType(NotFoundException.class) - .isThrownBy(() -> childAcl2.isGranted(CREATE, Arrays.asList((Sid) new PrincipalSid("joe")), false)); - } - - @Test - public void updatedAceValuesAreCorrectlyReflectedInAcl() { - Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_GENERAL"); - auth.setAuthenticated(true); - SecurityContextHolder.getContext().setAuthentication(auth); - MutableAcl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, false, - new PrincipalSid("joe")); - MockAclService service = new MockAclService(); - acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"), true); - acl.insertAce(1, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_USER_READ"), true); - acl.insertAce(2, BasePermission.CREATE, new PrincipalSid("ben"), true); - service.updateAcl(acl); - assertThat(BasePermission.READ).isEqualTo(acl.getEntries().get(0).getPermission()); - assertThat(BasePermission.WRITE).isEqualTo(acl.getEntries().get(1).getPermission()); - assertThat(BasePermission.CREATE).isEqualTo(acl.getEntries().get(2).getPermission()); - // Change each permission - acl.updateAce(0, BasePermission.CREATE); - acl.updateAce(1, BasePermission.DELETE); - acl.updateAce(2, BasePermission.READ); - // Check the change was successfully made - assertThat(BasePermission.CREATE).isEqualTo(acl.getEntries().get(0).getPermission()); - assertThat(BasePermission.DELETE).isEqualTo(acl.getEntries().get(1).getPermission()); - assertThat(BasePermission.READ).isEqualTo(acl.getEntries().get(2).getPermission()); - } - - @Test - public void auditableEntryFlagsAreUpdatedCorrectly() { - Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_AUDITING", "ROLE_GENERAL"); - auth.setAuthenticated(true); - SecurityContextHolder.getContext().setAuthentication(auth); - MutableAcl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, false, - new PrincipalSid("joe")); - MockAclService service = new MockAclService(); - acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"), true); - acl.insertAce(1, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_USER_READ"), true); - service.updateAcl(acl); - assertThat(((AuditableAccessControlEntry) acl.getEntries().get(0)).isAuditFailure()).isFalse(); - assertThat(((AuditableAccessControlEntry) acl.getEntries().get(1)).isAuditFailure()).isFalse(); - assertThat(((AuditableAccessControlEntry) acl.getEntries().get(0)).isAuditSuccess()).isFalse(); - assertThat(((AuditableAccessControlEntry) acl.getEntries().get(1)).isAuditSuccess()).isFalse(); - // Change each permission - ((AuditableAcl) acl).updateAuditing(0, true, true); - ((AuditableAcl) acl).updateAuditing(1, true, true); - // Check the change was successfuly made - assertThat(acl.getEntries()).extracting("auditSuccess").containsOnly(true, true); - assertThat(acl.getEntries()).extracting("auditFailure").containsOnly(true, true); - } - - @Test - public void gettersAndSettersAreConsistent() { - Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_GENERAL"); - auth.setAuthenticated(true); - SecurityContextHolder.getContext().setAuthentication(auth); - ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, (100)); - ObjectIdentity identity2 = new ObjectIdentityImpl(TARGET_CLASS, (101)); - MutableAcl acl = new AclImpl(identity, 1, this.authzStrategy, this.pgs, null, null, true, - new PrincipalSid("joe")); - MutableAcl parentAcl = new AclImpl(identity2, 2, this.authzStrategy, this.pgs, null, null, true, - new PrincipalSid("joe")); - MockAclService service = new MockAclService(); - acl.insertAce(0, BasePermission.READ, new GrantedAuthoritySid("ROLE_USER_READ"), true); - acl.insertAce(1, BasePermission.WRITE, new GrantedAuthoritySid("ROLE_USER_READ"), true); - service.updateAcl(acl); - assertThat(1).isEqualTo(acl.getId()); - assertThat(identity).isEqualTo(acl.getObjectIdentity()); - assertThat(new PrincipalSid("joe")).isEqualTo(acl.getOwner()); - assertThat(acl.getParentAcl()).isNull(); - assertThat(acl.isEntriesInheriting()).isTrue(); - assertThat(acl.getEntries()).hasSize(2); - acl.setParent(parentAcl); - assertThat(parentAcl).isEqualTo(acl.getParentAcl()); - acl.setEntriesInheriting(false); - assertThat(acl.isEntriesInheriting()).isFalse(); - acl.setOwner(new PrincipalSid("ben")); - assertThat(new PrincipalSid("ben")).isEqualTo(acl.getOwner()); - } - - @Test - public void isSidLoadedBehavesAsExpected() { - List loadedSids = Arrays.asList(new PrincipalSid("ben"), new GrantedAuthoritySid("ROLE_IGNORED")); - MutableAcl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, loadedSids, true, - new PrincipalSid("joe")); - assertThat(acl.isSidLoaded(loadedSids)).isTrue(); - assertThat(acl.isSidLoaded(Arrays.asList(new GrantedAuthoritySid("ROLE_IGNORED"), new PrincipalSid("ben")))) - .isTrue(); - assertThat(acl.isSidLoaded(Arrays.asList((Sid) new GrantedAuthoritySid("ROLE_IGNORED")))).isTrue(); - assertThat(acl.isSidLoaded(BEN)).isTrue(); - assertThat(acl.isSidLoaded(null)).isTrue(); - assertThat(acl.isSidLoaded(new ArrayList<>(0))).isTrue(); - assertThat(acl.isSidLoaded( - Arrays.asList(new GrantedAuthoritySid("ROLE_IGNORED"), new GrantedAuthoritySid("ROLE_IGNORED")))) - .isTrue(); - assertThat(acl.isSidLoaded( - Arrays.asList(new GrantedAuthoritySid("ROLE_GENERAL"), new GrantedAuthoritySid("ROLE_IGNORED")))) - .isFalse(); - assertThat(acl.isSidLoaded( - Arrays.asList(new GrantedAuthoritySid("ROLE_IGNORED"), new GrantedAuthoritySid("ROLE_GENERAL")))) - .isFalse(); - } - - @Test - public void insertAceRaisesNotFoundExceptionForIndexLessThanZero() { - AclImpl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, - new PrincipalSid("joe")); - assertThatExceptionOfType(NotFoundException.class) - .isThrownBy(() -> acl.insertAce(-1, mock(Permission.class), mock(Sid.class), true)); - } - - @Test - public void deleteAceRaisesNotFoundExceptionForIndexLessThanZero() { - AclImpl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, - new PrincipalSid("joe")); - assertThatExceptionOfType(NotFoundException.class).isThrownBy(() -> acl.deleteAce(-1)); - } - - @Test - public void insertAceRaisesNotFoundExceptionForIndexGreaterThanSize() { - AclImpl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, - new PrincipalSid("joe")); - // Insert at zero, OK. - acl.insertAce(0, mock(Permission.class), mock(Sid.class), true); - // Size is now 1 - assertThatExceptionOfType(NotFoundException.class) - .isThrownBy(() -> acl.insertAce(2, mock(Permission.class), mock(Sid.class), true)); - } - - // SEC-1151 - @Test - public void deleteAceRaisesNotFoundExceptionForIndexEqualToSize() { - AclImpl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, this.pgs, null, null, true, - new PrincipalSid("joe")); - acl.insertAce(0, mock(Permission.class), mock(Sid.class), true); - // Size is now 1 - assertThatExceptionOfType(NotFoundException.class).isThrownBy(() -> acl.deleteAce(1)); - } - - // SEC-1795 - @Test - public void changingParentIsSuccessful() { - AclImpl parentAcl = new AclImpl(this.objectIdentity, 1L, this.authzStrategy, this.mockAuditLogger); - AclImpl childAcl = new AclImpl(this.objectIdentity, 2L, this.authzStrategy, this.mockAuditLogger); - AclImpl changeParentAcl = new AclImpl(this.objectIdentity, 3L, this.authzStrategy, this.mockAuditLogger); - childAcl.setParent(parentAcl); - childAcl.setParent(changeParentAcl); - } - - // SEC-2342 - @Test - public void maskPermissionGrantingStrategy() { - DefaultPermissionGrantingStrategy maskPgs = new MaskPermissionGrantingStrategy(this.mockAuditLogger); - MockAclService service = new MockAclService(); - AclImpl acl = new AclImpl(this.objectIdentity, 1, this.authzStrategy, maskPgs, null, null, true, - new PrincipalSid("joe")); - Permission permission = this.permissionFactory - .buildFromMask(BasePermission.READ.getMask() | BasePermission.WRITE.getMask()); - Sid sid = new PrincipalSid("ben"); - acl.insertAce(0, permission, sid, true); - service.updateAcl(acl); - List permissions = Arrays.asList(BasePermission.READ); - List sids = Arrays.asList(sid); - assertThat(acl.isGranted(permissions, sids, false)).isTrue(); - } - - @Test - public void hashCodeWithoutStackOverFlow() throws Exception { - Sid sid = new PrincipalSid("pSid"); - ObjectIdentity oid = new ObjectIdentityImpl("type", 1); - AclAuthorizationStrategy authStrategy = new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("role")); - PermissionGrantingStrategy grantingStrategy = new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()); - AclImpl acl = new AclImpl(oid, 1L, authStrategy, grantingStrategy, null, null, false, sid); - AccessControlEntryImpl ace = new AccessControlEntryImpl(1L, acl, sid, BasePermission.READ, true, true, true); - Field fieldAces = FieldUtils.getField(AclImpl.class, "aces"); - fieldAces.setAccessible(true); - List aces = (List) fieldAces.get(acl); - aces.add(ace); - ace.hashCode(); - } - - private static class MaskPermissionGrantingStrategy extends DefaultPermissionGrantingStrategy { - - MaskPermissionGrantingStrategy(AuditLogger auditLogger) { - super(auditLogger); - } - - @Override - protected boolean isGranted(AccessControlEntry ace, Permission p) { - if (p.getMask() != 0) { - return (p.getMask() & ace.getPermission().getMask()) != 0; - } - return super.isGranted(ace, p); - } - - } - - private class MockAclService implements MutableAclService { - - @Override - public MutableAcl createAcl(ObjectIdentity objectIdentity) throws AlreadyExistsException { - return null; - } - - @Override - public void deleteAcl(ObjectIdentity objectIdentity, boolean deleteChildren) throws ChildrenExistException { - } - - /* - * Mock implementation that populates the aces list with fully initialized - * AccessControlEntries - * - * @see org.springframework.security.acls.MutableAclService#updateAcl(org. - * springframework .security.acls.MutableAcl) - */ - @Override - @SuppressWarnings("unchecked") - public MutableAcl updateAcl(MutableAcl acl) throws NotFoundException { - List oldAces = acl.getEntries(); - Field acesField = FieldUtils.getField(AclImpl.class, "aces"); - acesField.setAccessible(true); - List newAces; - try { - newAces = (List) acesField.get(acl); - newAces.clear(); - for (int i = 0; i < oldAces.size(); i++) { - AccessControlEntry ac = oldAces.get(i); - // Just give an ID to all this acl's aces, rest of the fields are just - // copied - newAces.add(new AccessControlEntryImpl((i + 1), ac.getAcl(), ac.getSid(), ac.getPermission(), - ac.isGranting(), ((AuditableAccessControlEntry) ac).isAuditSuccess(), - ((AuditableAccessControlEntry) ac).isAuditFailure())); - } - } - catch (IllegalAccessException ex) { - ex.printStackTrace(); - } - return acl; - } - - @Override - public List findChildren(ObjectIdentity parentIdentity) { - return null; - } - - @Override - public Acl readAclById(ObjectIdentity object) throws NotFoundException { - return null; - } - - @Override - public Acl readAclById(ObjectIdentity object, List sids) throws NotFoundException { - return null; - } - - @Override - public Map readAclsById(List objects) throws NotFoundException { - return null; - } - - @Override - public Map readAclsById(List objects, List sids) - throws NotFoundException { - return null; - } - - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/domain/AclImplementationSecurityCheckTests.java b/acl/src/test/java/org/springframework/security/acls/domain/AclImplementationSecurityCheckTests.java deleted file mode 100644 index c5ac4efd8cf..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/domain/AclImplementationSecurityCheckTests.java +++ /dev/null @@ -1,191 +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.domain; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.acls.model.Acl; -import org.springframework.security.acls.model.MutableAcl; -import org.springframework.security.acls.model.NotFoundException; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatNoException; - -/** - * Test class for {@link AclAuthorizationStrategyImpl} and {@link AclImpl} security - * checks. - * - * @author Andrei Stefan - */ -public class AclImplementationSecurityCheckTests { - - private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject"; - - @BeforeEach - public void setUp() { - SecurityContextHolder.clearContext(); - } - - @AfterEach - public void tearDown() { - SecurityContextHolder.clearContext(); - } - - @Test - public void testSecurityCheckNoACEs() { - Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_GENERAL", "ROLE_AUDITING", - "ROLE_OWNERSHIP"); - auth.setAuthenticated(true); - SecurityContextHolder.getContext().setAuthentication(auth); - ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100L); - AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl( - new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"), - new SimpleGrantedAuthority("ROLE_GENERAL")); - Acl acl = new AclImpl(identity, 1L, aclAuthorizationStrategy, new ConsoleAuditLogger()); - aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_GENERAL); - aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_AUDITING); - aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_OWNERSHIP); - // Create another authorization strategy - AclAuthorizationStrategy aclAuthorizationStrategy2 = new AclAuthorizationStrategyImpl( - new SimpleGrantedAuthority("ROLE_ONE"), new SimpleGrantedAuthority("ROLE_TWO"), - new SimpleGrantedAuthority("ROLE_THREE")); - Acl acl2 = new AclImpl(identity, 1L, aclAuthorizationStrategy2, new ConsoleAuditLogger()); - // Check access in case the principal has no authorization rights - assertThatExceptionOfType(NotFoundException.class) - .isThrownBy(() -> aclAuthorizationStrategy2.securityCheck(acl2, AclAuthorizationStrategy.CHANGE_GENERAL)); - assertThatExceptionOfType(NotFoundException.class) - .isThrownBy(() -> aclAuthorizationStrategy2.securityCheck(acl2, AclAuthorizationStrategy.CHANGE_AUDITING)); - assertThatExceptionOfType(NotFoundException.class) - .isThrownBy(() -> aclAuthorizationStrategy2.securityCheck(acl2, AclAuthorizationStrategy.CHANGE_OWNERSHIP)); - } - - @Test - public void testSecurityCheckWithMultipleACEs() { - // Create a simple authentication with ROLE_GENERAL - Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_GENERAL"); - auth.setAuthenticated(true); - SecurityContextHolder.getContext().setAuthentication(auth); - ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100L); - // Authorization strategy will require a different role for each access - AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl( - new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"), - new SimpleGrantedAuthority("ROLE_GENERAL")); - // Let's give the principal the ADMINISTRATION permission, without - // granting access - MutableAcl aclFirstDeny = new AclImpl(identity, 1L, aclAuthorizationStrategy, new ConsoleAuditLogger()); - aclFirstDeny.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(auth), false); - // The CHANGE_GENERAL test should pass as the principal has ROLE_GENERAL - aclAuthorizationStrategy.securityCheck(aclFirstDeny, AclAuthorizationStrategy.CHANGE_GENERAL); - // The CHANGE_AUDITING and CHANGE_OWNERSHIP should fail since the - // principal doesn't have these authorities, - // nor granting access - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy( - () -> aclAuthorizationStrategy.securityCheck(aclFirstDeny, AclAuthorizationStrategy.CHANGE_AUDITING)); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy( - () -> aclAuthorizationStrategy.securityCheck(aclFirstDeny, AclAuthorizationStrategy.CHANGE_OWNERSHIP)); - // Add granting access to this principal - aclFirstDeny.insertAce(1, BasePermission.ADMINISTRATION, new PrincipalSid(auth), true); - // and try again for CHANGE_AUDITING - the first ACE's granting flag - // (false) will deny this access - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy( - () -> aclAuthorizationStrategy.securityCheck(aclFirstDeny, AclAuthorizationStrategy.CHANGE_AUDITING)); - // Create another ACL and give the principal the ADMINISTRATION - // permission, with granting access - MutableAcl aclFirstAllow = new AclImpl(identity, 1L, aclAuthorizationStrategy, new ConsoleAuditLogger()); - aclFirstAllow.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(auth), true); - // The CHANGE_AUDITING test should pass as there is one ACE with - // granting access - aclAuthorizationStrategy.securityCheck(aclFirstAllow, AclAuthorizationStrategy.CHANGE_AUDITING); - // Add a deny ACE and test again for CHANGE_AUDITING - aclFirstAllow.insertAce(1, BasePermission.ADMINISTRATION, new PrincipalSid(auth), false); - assertThatNoException().isThrownBy( - () -> aclAuthorizationStrategy.securityCheck(aclFirstAllow, AclAuthorizationStrategy.CHANGE_AUDITING)); - // Create an ACL with no ACE - MutableAcl aclNoACE = new AclImpl(identity, 1L, aclAuthorizationStrategy, new ConsoleAuditLogger()); - assertThatExceptionOfType(NotFoundException.class).isThrownBy( - () -> aclAuthorizationStrategy.securityCheck(aclNoACE, AclAuthorizationStrategy.CHANGE_AUDITING)); - // and still grant access for CHANGE_GENERAL - assertThatNoException().isThrownBy( - () -> aclAuthorizationStrategy.securityCheck(aclNoACE, AclAuthorizationStrategy.CHANGE_GENERAL)); - } - - @Test - public void testSecurityCheckWithInheritableACEs() { - // Create a simple authentication with ROLE_GENERAL - Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_GENERAL"); - auth.setAuthenticated(true); - SecurityContextHolder.getContext().setAuthentication(auth); - ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100); - // Authorization strategy will require a different role for each access - AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl( - new SimpleGrantedAuthority("ROLE_ONE"), new SimpleGrantedAuthority("ROLE_TWO"), - new SimpleGrantedAuthority("ROLE_GENERAL")); - // Let's give the principal an ADMINISTRATION permission, with granting - // access - MutableAcl parentAcl = new AclImpl(identity, 1, aclAuthorizationStrategy, new ConsoleAuditLogger()); - parentAcl.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(auth), true); - MutableAcl childAcl = new AclImpl(identity, 2, aclAuthorizationStrategy, new ConsoleAuditLogger()); - // Check against the 'child' acl, which doesn't offer any authorization - // rights on CHANGE_OWNERSHIP - assertThatExceptionOfType(NotFoundException.class).isThrownBy( - () -> aclAuthorizationStrategy.securityCheck(childAcl, AclAuthorizationStrategy.CHANGE_OWNERSHIP)); - // Link the child with its parent and test again against the - // CHANGE_OWNERSHIP right - childAcl.setParent(parentAcl); - childAcl.setEntriesInheriting(true); - assertThatNoException().isThrownBy( - () -> aclAuthorizationStrategy.securityCheck(childAcl, AclAuthorizationStrategy.CHANGE_OWNERSHIP)); - // Create a root parent and link it to the middle parent - MutableAcl rootParentAcl = new AclImpl(identity, 1, aclAuthorizationStrategy, new ConsoleAuditLogger()); - parentAcl = new AclImpl(identity, 1, aclAuthorizationStrategy, new ConsoleAuditLogger()); - rootParentAcl.insertAce(0, BasePermission.ADMINISTRATION, new PrincipalSid(auth), true); - parentAcl.setEntriesInheriting(true); - parentAcl.setParent(rootParentAcl); - childAcl.setParent(parentAcl); - assertThatNoException().isThrownBy( - () -> aclAuthorizationStrategy.securityCheck(childAcl, AclAuthorizationStrategy.CHANGE_OWNERSHIP)); - } - - @Test - public void testSecurityCheckPrincipalOwner() { - Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_ONE"); - auth.setAuthenticated(true); - SecurityContextHolder.getContext().setAuthentication(auth); - ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100); - AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl( - new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"), - new SimpleGrantedAuthority("ROLE_GENERAL")); - Acl acl = new AclImpl(identity, 1, aclAuthorizationStrategy, - new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()), null, null, false, - new PrincipalSid(auth)); - assertThatNoException() - .isThrownBy(() -> aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_GENERAL)); - assertThatExceptionOfType(NotFoundException.class) - .isThrownBy(() -> aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_AUDITING)); - assertThatNoException() - .isThrownBy(() -> aclAuthorizationStrategy.securityCheck(acl, AclAuthorizationStrategy.CHANGE_OWNERSHIP)); - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/domain/AuditLoggerTests.java b/acl/src/test/java/org/springframework/security/acls/domain/AuditLoggerTests.java deleted file mode 100644 index 5fc0c0b7795..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/domain/AuditLoggerTests.java +++ /dev/null @@ -1,97 +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.domain; - -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.acls.model.AccessControlEntry; -import org.springframework.security.acls.model.AuditableAccessControlEntry; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Test class for {@link ConsoleAuditLogger}. - * - * @author Andrei Stefan - */ -public class AuditLoggerTests { - - private PrintStream console; - - private ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - - private ConsoleAuditLogger logger; - - private AuditableAccessControlEntry ace; - - @BeforeEach - public void setUp() { - this.logger = new ConsoleAuditLogger(); - this.ace = mock(AuditableAccessControlEntry.class); - this.console = System.out; - System.setOut(new PrintStream(this.bytes)); - } - - @AfterEach - public void tearDown() { - System.setOut(this.console); - this.bytes.reset(); - } - - @Test - public void nonAuditableAceIsIgnored() { - AccessControlEntry ace = mock(AccessControlEntry.class); - this.logger.logIfNeeded(true, ace); - assertThat(this.bytes.size()).isZero(); - } - - @Test - public void successIsNotLoggedIfAceDoesntRequireSuccessAudit() { - given(this.ace.isAuditSuccess()).willReturn(false); - this.logger.logIfNeeded(true, this.ace); - assertThat(this.bytes.size()).isZero(); - } - - @Test - public void successIsLoggedIfAceRequiresSuccessAudit() { - given(this.ace.isAuditSuccess()).willReturn(true); - this.logger.logIfNeeded(true, this.ace); - assertThat(this.bytes.toString()).startsWith("GRANTED due to ACE"); - } - - @Test - public void failureIsntLoggedIfAceDoesntRequireFailureAudit() { - given(this.ace.isAuditFailure()).willReturn(false); - this.logger.logIfNeeded(false, this.ace); - assertThat(this.bytes.size()).isZero(); - } - - @Test - public void failureIsLoggedIfAceRequiresFailureAudit() { - given(this.ace.isAuditFailure()).willReturn(true); - this.logger.logIfNeeded(false, this.ace); - assertThat(this.bytes.toString()).startsWith("DENIED due to ACE"); - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/domain/ObjectIdentityImplTests.java b/acl/src/test/java/org/springframework/security/acls/domain/ObjectIdentityImplTests.java deleted file mode 100644 index 6037b42a4a0..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/domain/ObjectIdentityImplTests.java +++ /dev/null @@ -1,154 +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.domain; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.acls.model.ObjectIdentity; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.assertThatNoException; - -/** - * Tests for {@link ObjectIdentityImpl}. - * - * @author Andrei Stefan - */ -@SuppressWarnings("unused") -public class ObjectIdentityImplTests { - - private static final String DOMAIN_CLASS = "org.springframework.security.acls.domain.ObjectIdentityImplTests$MockIdDomainObject"; - - @Test - public void constructorsRespectRequiredFields() { - // Check one-argument constructor required field - assertThatIllegalArgumentException().isThrownBy(() -> new ObjectIdentityImpl(null)); - // Check String-Serializable constructor required field - assertThatIllegalArgumentException().isThrownBy(() -> new ObjectIdentityImpl("", 1L)); - // Check Serializable parameter is not null - assertThatIllegalArgumentException().isThrownBy(() -> new ObjectIdentityImpl(DOMAIN_CLASS, null)); - // The correct way of using String-Serializable constructor - assertThatNoException().isThrownBy(() -> new ObjectIdentityImpl(DOMAIN_CLASS, 1L)); - // Check the Class-Serializable constructor - assertThatIllegalArgumentException().isThrownBy(() -> new ObjectIdentityImpl(MockIdDomainObject.class, null)); - } - - @Test - public void gettersReturnExpectedValues() { - ObjectIdentity obj = new ObjectIdentityImpl(DOMAIN_CLASS, 1L); - assertThat(obj.getIdentifier()).isEqualTo(1L); - assertThat(obj.getType()).isEqualTo(MockIdDomainObject.class.getName()); - } - - @Test - public void testGetIdMethodConstraints() { - // Check the getId() method is present - assertThatExceptionOfType(IdentityUnavailableException.class) - .isThrownBy(() -> new ObjectIdentityImpl("A_STRING_OBJECT")); - // getId() should return a non-null value - MockIdDomainObject mockId = new MockIdDomainObject(); - assertThatIllegalArgumentException().isThrownBy(() -> new ObjectIdentityImpl(mockId)); - // getId() should return a Serializable object - mockId.setId(new MockIdDomainObject()); - assertThatIllegalArgumentException().isThrownBy(() -> new ObjectIdentityImpl(mockId)); - // getId() should return a Serializable object - mockId.setId(100L); - assertThatNoException().isThrownBy(() -> new ObjectIdentityImpl(mockId)); - } - - @Test - public void constructorRejectsInvalidTypeParameter() { - assertThatIllegalArgumentException().isThrownBy(() -> new ObjectIdentityImpl("", 1L)); - } - - @Test - public void testEquals() { - ObjectIdentity obj = new ObjectIdentityImpl(DOMAIN_CLASS, 1L); - MockIdDomainObject mockObj = new MockIdDomainObject(); - mockObj.setId(1L); - String string = "SOME_STRING"; - assertThat(string).isNotSameAs(obj); - assertThat(obj).isNotNull(); - assertThat(obj).isNotEqualTo("DIFFERENT_OBJECT_TYPE"); - assertThat(obj).isNotEqualTo(new ObjectIdentityImpl(DOMAIN_CLASS, 2L)); - assertThat(obj).isNotEqualTo(new ObjectIdentityImpl( - "org.springframework.security.acls.domain.ObjectIdentityImplTests$MockOtherIdDomainObject", 1L)); - assertThat(new ObjectIdentityImpl(DOMAIN_CLASS, 1L)).isEqualTo(obj); - assertThat(new ObjectIdentityImpl(mockObj)).isEqualTo(obj); - } - - @Test - public void hashcodeIsDifferentForDifferentJavaTypes() { - ObjectIdentity obj = new ObjectIdentityImpl(Object.class, 1L); - ObjectIdentity obj2 = new ObjectIdentityImpl(String.class, 1L); - assertThat(obj.hashCode()).isNotEqualTo(obj2.hashCode()); - } - - @Test - public void longAndIntegerIdsWithSameValueAreEqualAndHaveSameHashcode() { - ObjectIdentity obj = new ObjectIdentityImpl(Object.class, 5L); - ObjectIdentity obj2 = new ObjectIdentityImpl(Object.class, 5); - assertThat(obj2).isEqualTo(obj); - assertThat(obj2.hashCode()).isEqualTo(obj.hashCode()); - } - - @Test - public void equalStringIdsAreEqualAndHaveSameHashcode() { - ObjectIdentity obj = new ObjectIdentityImpl(Object.class, "1000"); - ObjectIdentity obj2 = new ObjectIdentityImpl(Object.class, "1000"); - assertThat(obj2).isEqualTo(obj); - assertThat(obj2.hashCode()).isEqualTo(obj.hashCode()); - } - - @Test - public void stringAndNumericIdsAreNotEqual() { - ObjectIdentity obj = new ObjectIdentityImpl(Object.class, "1000"); - ObjectIdentity obj2 = new ObjectIdentityImpl(Object.class, 1000L); - assertThat(obj).isNotEqualTo(obj2); - } - - private class MockIdDomainObject { - - private Object id; - - public Object getId() { - return this.id; - } - - public void setId(Object id) { - this.id = id; - } - - } - - private class MockOtherIdDomainObject { - - private Object id; - - public Object getId() { - return this.id; - } - - public void setId(Object id) { - this.id = id; - } - - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/domain/ObjectIdentityRetrievalStrategyImplTests.java b/acl/src/test/java/org/springframework/security/acls/domain/ObjectIdentityRetrievalStrategyImplTests.java deleted file mode 100644 index 0c0686ca06e..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/domain/ObjectIdentityRetrievalStrategyImplTests.java +++ /dev/null @@ -1,58 +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.domain; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.ObjectIdentityRetrievalStrategy; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ObjectIdentityRetrievalStrategyImpl} - * - * @author Andrei Stefan - */ -public class ObjectIdentityRetrievalStrategyImplTests { - - @Test - public void testObjectIdentityCreation() { - MockIdDomainObject domain = new MockIdDomainObject(); - domain.setId(1); - ObjectIdentityRetrievalStrategy retStrategy = new ObjectIdentityRetrievalStrategyImpl(); - ObjectIdentity identity = retStrategy.getObjectIdentity(domain); - assertThat(identity).isNotNull(); - assertThat(new ObjectIdentityImpl(domain)).isEqualTo(identity); - } - - @SuppressWarnings("unused") - private class MockIdDomainObject { - - private Object id; - - public Object getId() { - return this.id; - } - - public void setId(Object id) { - this.id = id; - } - - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/domain/PermissionTests.java b/acl/src/test/java/org/springframework/security/acls/domain/PermissionTests.java deleted file mode 100644 index 6a1d6eaff45..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/domain/PermissionTests.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.domain; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.acls.model.Permission; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests classes associated with Permission. - * - * @author Ben Alex - */ -public class PermissionTests { - - private DefaultPermissionFactory permissionFactory; - - @BeforeEach - public void createPermissionfactory() { - this.permissionFactory = new DefaultPermissionFactory(); - } - - @Test - public void basePermissionTest() { - Permission p = this.permissionFactory.buildFromName("WRITE"); - assertThat(p).isNotNull(); - } - - @Test - public void expectedIntegerValues() { - assertThat(BasePermission.READ.getMask()).isEqualTo(1); - assertThat(BasePermission.ADMINISTRATION.getMask()).isEqualTo(16); - assertThat(new CumulativePermission().set(BasePermission.READ) - .set(BasePermission.WRITE) - .set(BasePermission.CREATE) - .getMask()).isEqualTo(7); - assertThat(new CumulativePermission().set(BasePermission.READ).set(BasePermission.ADMINISTRATION).getMask()) - .isEqualTo(17); - } - - @Test - public void fromInteger() { - Permission permission = this.permissionFactory.buildFromMask(7); - permission = this.permissionFactory.buildFromMask(4); - } - - @Test - public void stringConversion() { - this.permissionFactory.registerPublicPermissions(SpecialPermission.class); - assertThat(BasePermission.READ.toString()).isEqualTo("BasePermission[...............................R=1]"); - assertThat(BasePermission.ADMINISTRATION.toString()) - .isEqualTo("BasePermission[...........................A....=16]"); - assertThat(new CumulativePermission().set(BasePermission.READ).toString()) - .isEqualTo("CumulativePermission[...............................R=1]"); - assertThat( - new CumulativePermission().set(SpecialPermission.ENTER).set(BasePermission.ADMINISTRATION).toString()) - .isEqualTo("CumulativePermission[..........................EA....=48]"); - assertThat(new CumulativePermission().set(BasePermission.ADMINISTRATION).set(BasePermission.READ).toString()) - .isEqualTo("CumulativePermission[...........................A...R=17]"); - assertThat(new CumulativePermission().set(BasePermission.ADMINISTRATION) - .set(BasePermission.READ) - .clear(BasePermission.ADMINISTRATION) - .toString()).isEqualTo("CumulativePermission[...............................R=1]"); - assertThat(new CumulativePermission().set(BasePermission.ADMINISTRATION) - .set(BasePermission.READ) - .clear(BasePermission.ADMINISTRATION) - .clear(BasePermission.READ) - .toString()).isEqualTo("CumulativePermission[................................=0]"); - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/domain/SpecialPermission.java b/acl/src/test/java/org/springframework/security/acls/domain/SpecialPermission.java deleted file mode 100644 index fb11be30085..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/domain/SpecialPermission.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.domain; - -import org.springframework.security.acls.model.Permission; - -/** - * A test permission. - * - * @author Ben Alex - */ -public class SpecialPermission extends BasePermission { - - public static final Permission ENTER = new SpecialPermission(1 << 5, 'E'); // 32 - - public static final Permission LEAVE = new SpecialPermission(1 << 6, 'L'); - - protected SpecialPermission(int mask, char code) { - super(mask, code); - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/jdbc/AbstractBasicLookupStrategyTests.java b/acl/src/test/java/org/springframework/security/acls/jdbc/AbstractBasicLookupStrategyTests.java deleted file mode 100644 index 0eda3d54635..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/jdbc/AbstractBasicLookupStrategyTests.java +++ /dev/null @@ -1,360 +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.jdbc; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import javax.sql.DataSource; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; -import org.springframework.cache.concurrent.ConcurrentMapCache; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.security.acls.TargetObject; -import org.springframework.security.acls.TargetObjectWithUUID; -import org.springframework.security.acls.domain.AclAuthorizationStrategy; -import org.springframework.security.acls.domain.AclAuthorizationStrategyImpl; -import org.springframework.security.acls.domain.BasePermission; -import org.springframework.security.acls.domain.ConsoleAuditLogger; -import org.springframework.security.acls.domain.DefaultPermissionFactory; -import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy; -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.domain.SpringCacheBasedAclCache; -import org.springframework.security.acls.model.Acl; -import org.springframework.security.acls.model.AuditableAccessControlEntry; -import org.springframework.security.acls.model.MutableAcl; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.Permission; -import org.springframework.security.acls.model.Sid; -import org.springframework.security.core.authority.SimpleGrantedAuthority; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests {@link BasicLookupStrategy} - * - * @author Andrei Stefan - */ -public abstract class AbstractBasicLookupStrategyTests { - - protected static final Sid BEN_SID = new PrincipalSid("ben"); - - protected static final String TARGET_CLASS = TargetObject.class.getName(); - - protected static final String TARGET_CLASS_WITH_UUID = TargetObjectWithUUID.class.getName(); - - protected static final UUID OBJECT_IDENTITY_UUID = UUID.randomUUID(); - - protected static final Long OBJECT_IDENTITY_LONG_AS_UUID = 110L; - - private BasicLookupStrategy strategy; - - private static CacheManagerMock cacheManager; - - public abstract JdbcTemplate getJdbcTemplate(); - - public abstract DataSource getDataSource(); - - @BeforeAll - public static void initCacheManaer() { - cacheManager = new CacheManagerMock(); - cacheManager.addCache("basiclookuptestcache"); - } - - @AfterAll - public static void shutdownCacheManager() { - cacheManager.clear(); - } - - @BeforeEach - public void populateDatabase() { - String query = "INSERT INTO acl_sid(ID,PRINCIPAL,SID) VALUES (1,1,'ben');" - + "INSERT INTO acl_class(ID,CLASS) VALUES (2,'" + TARGET_CLASS + "');" - + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (1,2,100,null,1,1);" - + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (2,2,101,1,1,1);" - + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (3,2,102,2,1,1);" - + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (1,1,0,1,1,1,0,0);" - + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (2,1,1,1,2,0,0,0);" - + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (3,2,0,1,8,1,0,0);" - + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (4,3,0,1,8,0,0,0);"; - getJdbcTemplate().execute(query); - } - - @BeforeEach - public void initializeBeans() { - this.strategy = new BasicLookupStrategy(getDataSource(), aclCache(), aclAuthStrategy(), - new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger())); - this.strategy.setPermissionFactory(new DefaultPermissionFactory()); - } - - protected AclAuthorizationStrategy aclAuthStrategy() { - return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ADMINISTRATOR")); - } - - protected SpringCacheBasedAclCache aclCache() { - return new SpringCacheBasedAclCache(getCache(), new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()), - new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_USER"))); - } - - protected Cache getCache() { - Cache cache = cacheManager.getCacheManager().getCache("basiclookuptestcache"); - cache.clear(); - return cache; - } - - @AfterEach - public void emptyDatabase() { - String query = "DELETE FROM acl_entry;" + "DELETE FROM acl_object_identity WHERE ID = 9;" - + "DELETE FROM acl_object_identity WHERE ID = 8;" + "DELETE FROM acl_object_identity WHERE ID = 7;" - + "DELETE FROM acl_object_identity WHERE ID = 6;" + "DELETE FROM acl_object_identity WHERE ID = 5;" - + "DELETE FROM acl_object_identity WHERE ID = 4;" + "DELETE FROM acl_object_identity WHERE ID = 3;" - + "DELETE FROM acl_object_identity WHERE ID = 2;" + "DELETE FROM acl_object_identity WHERE ID = 1;" - + "DELETE FROM acl_class;" + "DELETE FROM acl_sid;"; - getJdbcTemplate().execute(query); - } - - @Test - public void testAclsRetrievalWithDefaultBatchSize() throws Exception { - ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, 100L); - ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, 101L); - // Deliberately use an integer for the child, to reproduce bug report in SEC-819 - ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, 102); - Map map = this.strategy - .readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null); - checkEntries(topParentOid, middleParentOid, childOid, map); - } - - @Test - public void testAclsRetrievalFromCacheOnly() throws Exception { - ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, 100); - ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, 101L); - ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, 102L); - // Objects were put in cache - this.strategy.readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null); - // Let's empty the database to force acls retrieval from cache - emptyDatabase(); - Map map = this.strategy - .readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null); - checkEntries(topParentOid, middleParentOid, childOid, map); - } - - @Test - public void testAclsRetrievalWithCustomBatchSize() throws Exception { - ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, 100L); - ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, 101); - ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, 102L); - // Set a batch size to allow multiple database queries in order to retrieve all - // acls - this.strategy.setBatchSize(1); - Map map = this.strategy - .readAclsById(Arrays.asList(topParentOid, middleParentOid, childOid), null); - checkEntries(topParentOid, middleParentOid, childOid, map); - } - - private void checkEntries(ObjectIdentity topParentOid, ObjectIdentity middleParentOid, ObjectIdentity childOid, - Map map) { - assertThat(map).hasSize(3); - MutableAcl topParent = (MutableAcl) map.get(topParentOid); - MutableAcl middleParent = (MutableAcl) map.get(middleParentOid); - MutableAcl child = (MutableAcl) map.get(childOid); - // Check the retrieved versions has IDs - assertThat(topParent.getId()).isNotNull(); - assertThat(middleParent.getId()).isNotNull(); - assertThat(child.getId()).isNotNull(); - // Check their parents were correctly retrieved - assertThat(topParent.getParentAcl()).isNull(); - assertThat(middleParent.getParentAcl().getObjectIdentity()).isEqualTo(topParentOid); - assertThat(child.getParentAcl().getObjectIdentity()).isEqualTo(middleParentOid); - // Check their ACEs were correctly retrieved - assertThat(topParent.getEntries()).hasSize(2); - assertThat(middleParent.getEntries()).hasSize(1); - assertThat(child.getEntries()).hasSize(1); - // Check object identities were correctly retrieved - assertThat(topParent.getObjectIdentity()).isEqualTo(topParentOid); - assertThat(middleParent.getObjectIdentity()).isEqualTo(middleParentOid); - assertThat(child.getObjectIdentity()).isEqualTo(childOid); - // Check each entry - assertThat(topParent.isEntriesInheriting()).isTrue(); - assertThat(Long.valueOf(1)).isEqualTo(topParent.getId()); - assertThat(new PrincipalSid("ben")).isEqualTo(topParent.getOwner()); - assertThat(Long.valueOf(1)).isEqualTo(topParent.getEntries().get(0).getId()); - assertThat(topParent.getEntries().get(0).getPermission()).isEqualTo(BasePermission.READ); - assertThat(topParent.getEntries().get(0).getSid()).isEqualTo(new PrincipalSid("ben")); - assertThat(((AuditableAccessControlEntry) topParent.getEntries().get(0)).isAuditFailure()).isFalse(); - assertThat(((AuditableAccessControlEntry) topParent.getEntries().get(0)).isAuditSuccess()).isFalse(); - assertThat((topParent.getEntries().get(0)).isGranting()).isTrue(); - assertThat(Long.valueOf(2)).isEqualTo(topParent.getEntries().get(1).getId()); - assertThat(topParent.getEntries().get(1).getPermission()).isEqualTo(BasePermission.WRITE); - assertThat(topParent.getEntries().get(1).getSid()).isEqualTo(new PrincipalSid("ben")); - assertThat(((AuditableAccessControlEntry) topParent.getEntries().get(1)).isAuditFailure()).isFalse(); - assertThat(((AuditableAccessControlEntry) topParent.getEntries().get(1)).isAuditSuccess()).isFalse(); - assertThat(topParent.getEntries().get(1).isGranting()).isFalse(); - assertThat(middleParent.isEntriesInheriting()).isTrue(); - assertThat(Long.valueOf(2)).isEqualTo(middleParent.getId()); - assertThat(new PrincipalSid("ben")).isEqualTo(middleParent.getOwner()); - assertThat(Long.valueOf(3)).isEqualTo(middleParent.getEntries().get(0).getId()); - assertThat(middleParent.getEntries().get(0).getPermission()).isEqualTo(BasePermission.DELETE); - assertThat(middleParent.getEntries().get(0).getSid()).isEqualTo(new PrincipalSid("ben")); - assertThat(((AuditableAccessControlEntry) middleParent.getEntries().get(0)).isAuditFailure()).isFalse(); - assertThat(((AuditableAccessControlEntry) middleParent.getEntries().get(0)).isAuditSuccess()).isFalse(); - assertThat(middleParent.getEntries().get(0).isGranting()).isTrue(); - assertThat(child.isEntriesInheriting()).isTrue(); - assertThat(Long.valueOf(3)).isEqualTo(child.getId()); - assertThat(new PrincipalSid("ben")).isEqualTo(child.getOwner()); - assertThat(Long.valueOf(4)).isEqualTo(child.getEntries().get(0).getId()); - assertThat(child.getEntries().get(0).getPermission()).isEqualTo(BasePermission.DELETE); - assertThat(new PrincipalSid("ben")).isEqualTo(child.getEntries().get(0).getSid()); - assertThat(((AuditableAccessControlEntry) child.getEntries().get(0)).isAuditFailure()).isFalse(); - assertThat(((AuditableAccessControlEntry) child.getEntries().get(0)).isAuditSuccess()).isFalse(); - assertThat((child.getEntries().get(0)).isGranting()).isFalse(); - } - - @Test - public void testAllParentsAreRetrievedWhenChildIsLoaded() { - String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (6,2,103,1,1,1);"; - getJdbcTemplate().execute(query); - ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, 100L); - ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, 101L); - ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, 102L); - ObjectIdentity middleParent2Oid = new ObjectIdentityImpl(TARGET_CLASS, 103L); - // Retrieve the child - Map map = this.strategy.readAclsById(Arrays.asList(childOid), null); - // Check that the child and all its parents were retrieved - assertThat(map.get(childOid)).isNotNull(); - assertThat(map.get(childOid).getObjectIdentity()).isEqualTo(childOid); - assertThat(map.get(middleParentOid)).isNotNull(); - assertThat(map.get(middleParentOid).getObjectIdentity()).isEqualTo(middleParentOid); - assertThat(map.get(topParentOid)).isNotNull(); - assertThat(map.get(topParentOid).getObjectIdentity()).isEqualTo(topParentOid); - // The second parent shouldn't have been retrieved - assertThat(map.get(middleParent2Oid)).isNull(); - } - - /** - * Test created from SEC-590. - */ - @Test - public void testReadAllObjectIdentitiesWhenLastElementIsAlreadyCached() { - String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (6,2,105,null,1,1);" - + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (7,2,106,6,1,1);" - + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (8,2,107,6,1,1);" - + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (9,2,108,7,1,1);" - + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (7,6,0,1,1,1,0,0)"; - getJdbcTemplate().execute(query); - ObjectIdentity grandParentOid = new ObjectIdentityImpl(TARGET_CLASS, 104L); - ObjectIdentity parent1Oid = new ObjectIdentityImpl(TARGET_CLASS, 105L); - ObjectIdentity parent2Oid = new ObjectIdentityImpl(TARGET_CLASS, 106); - ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, 107); - // First lookup only child, thus populating the cache with grandParent, - // parent1 - // and child - List checkPermission = Arrays.asList(BasePermission.READ); - List sids = Arrays.asList(BEN_SID); - List childOids = Arrays.asList(childOid); - this.strategy.setBatchSize(6); - Map foundAcls = this.strategy.readAclsById(childOids, sids); - Acl foundChildAcl = foundAcls.get(childOid); - assertThat(foundChildAcl).isNotNull(); - assertThat(foundChildAcl.isGranted(checkPermission, sids, false)).isTrue(); - // Search for object identities has to be done in the following order: - // last - // element have to be one which - // is already in cache and the element before it must not be stored in - // cache - List allOids = Arrays.asList(grandParentOid, parent1Oid, parent2Oid, childOid); - foundAcls = this.strategy.readAclsById(allOids, sids); - Acl foundParent2Acl = foundAcls.get(parent2Oid); - assertThat(foundParent2Acl).isNotNull(); - assertThat(foundParent2Acl.isGranted(checkPermission, sids, false)).isTrue(); - } - - @Test - public void nullOwnerIsNotSupported() { - String query = "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (6,2,104,null,null,1);"; - getJdbcTemplate().execute(query); - ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS, 104L); - assertThatIllegalArgumentException() - .isThrownBy(() -> this.strategy.readAclsById(Arrays.asList(oid), Arrays.asList(BEN_SID))); - } - - @Test - public void testCreatePrincipalSid() { - Sid result = this.strategy.createSid(true, "sid"); - assertThat(result.getClass()).isEqualTo(PrincipalSid.class); - assertThat(((PrincipalSid) result).getPrincipal()).isEqualTo("sid"); - } - - @Test - public void testCreateGrantedAuthority() { - Sid result = this.strategy.createSid(false, "sid"); - assertThat(result.getClass()).isEqualTo(GrantedAuthoritySid.class); - assertThat(((GrantedAuthoritySid) result).getGrantedAuthority()).isEqualTo("sid"); - } - - @Test - public void setObjectIdentityGeneratorWhenNullThenThrowsIllegalArgumentException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.strategy.setObjectIdentityGenerator(null)) - .withMessage("objectIdentityGenerator cannot be null"); - // @formatter:on - } - - private static final class CacheManagerMock { - - private final List cacheNames; - - private final CacheManager cacheManager; - - private CacheManagerMock() { - this.cacheNames = new ArrayList<>(); - this.cacheManager = mock(CacheManager.class); - given(this.cacheManager.getCacheNames()).willReturn(this.cacheNames); - } - - private CacheManager getCacheManager() { - return this.cacheManager; - } - - private void addCache(String name) { - this.cacheNames.add(name); - Cache cache = new ConcurrentMapCache(name); - given(this.cacheManager.getCache(name)).willReturn(cache); - } - - private void clear() { - this.cacheNames.clear(); - } - - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/jdbc/AclClassIdUtilsTests.java b/acl/src/test/java/org/springframework/security/acls/jdbc/AclClassIdUtilsTests.java deleted file mode 100644 index 6c7c31cfee0..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/jdbc/AclClassIdUtilsTests.java +++ /dev/null @@ -1,139 +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.jdbc; - -import java.io.Serializable; -import java.math.BigInteger; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.UUID; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.core.convert.ConversionService; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.BDDMockito.given; - -/** - * Tests for {@link AclClassIdUtils}. - * - * @author paulwheeler - */ -@ExtendWith(MockitoExtension.class) -public class AclClassIdUtilsTests { - - private static final Long DEFAULT_IDENTIFIER = 999L; - - private static final BigInteger BIGINT_IDENTIFIER = new BigInteger("999"); - - private static final String DEFAULT_IDENTIFIER_AS_STRING = DEFAULT_IDENTIFIER.toString(); - - @Mock - private ResultSet resultSet; - - @Mock - private ConversionService conversionService; - - private AclClassIdUtils aclClassIdUtils; - - @BeforeEach - public void setUp() { - this.aclClassIdUtils = new AclClassIdUtils(); - } - - @Test - public void shouldReturnLongIfIdentifierIsLong() throws SQLException { - Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER, this.resultSet); - assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER); - } - - @Test - public void shouldReturnLongIfIdentifierIsBigInteger() throws SQLException { - Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(BIGINT_IDENTIFIER, this.resultSet); - assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER); - } - - @Test - public void shouldReturnLongIfClassIdTypeIsNull() throws SQLException { - given(this.resultSet.getString("class_id_type")).willReturn(null); - Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER_AS_STRING, this.resultSet); - assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER); - } - - @Test - public void shouldReturnLongIfNoClassIdTypeColumn() throws SQLException { - given(this.resultSet.getString("class_id_type")).willThrow(SQLException.class); - Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER_AS_STRING, this.resultSet); - assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER); - } - - @Test - public void shouldReturnLongIfTypeClassNotFound() throws SQLException { - given(this.resultSet.getString("class_id_type")).willReturn("com.example.UnknownType"); - Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER_AS_STRING, this.resultSet); - assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER); - } - - @Test - public void shouldReturnLongEvenIfCustomConversionServiceDoesNotSupportLongConversion() throws SQLException { - given(this.resultSet.getString("class_id_type")).willReturn("java.lang.Long"); - given(this.conversionService.canConvert(String.class, Long.class)).willReturn(false); - this.aclClassIdUtils.setConversionService(this.conversionService); - Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER_AS_STRING, this.resultSet); - assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER); - } - - @Test - public void shouldReturnLongWhenLongClassIdType() throws SQLException { - given(this.resultSet.getString("class_id_type")).willReturn("java.lang.Long"); - Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(DEFAULT_IDENTIFIER_AS_STRING, this.resultSet); - assertThat(newIdentifier).isEqualTo(DEFAULT_IDENTIFIER); - } - - @Test - public void shouldReturnUUIDWhenUUIDClassIdType() throws SQLException { - UUID identifier = UUID.randomUUID(); - given(this.resultSet.getString("class_id_type")).willReturn("java.util.UUID"); - Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(identifier.toString(), this.resultSet); - assertThat(newIdentifier).isEqualTo(identifier); - } - - @Test - public void shouldReturnStringWhenStringClassIdType() throws SQLException { - String identifier = "MY_STRING_IDENTIFIER"; - given(this.resultSet.getString("class_id_type")).willReturn("java.lang.String"); - Serializable newIdentifier = this.aclClassIdUtils.identifierFrom(identifier, this.resultSet); - assertThat(newIdentifier).isEqualTo(identifier); - } - - @Test - public void shouldNotAcceptNullConversionServiceInConstruction() { - assertThatIllegalArgumentException().isThrownBy(() -> new AclClassIdUtils(null)); - } - - @Test - public void shouldNotAcceptNullConversionServiceInSetter() { - assertThatIllegalArgumentException().isThrownBy(() -> this.aclClassIdUtils.setConversionService(null)); - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTests.java b/acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTests.java deleted file mode 100644 index 935a15ae2da..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTests.java +++ /dev/null @@ -1,56 +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.jdbc; - -import javax.sql.DataSource; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; - -import org.springframework.jdbc.core.JdbcTemplate; - -/** - * Tests {@link BasicLookupStrategy} with Acl Class type id not specified. - * - * @author Andrei Stefan - * @author Paul Wheeler - */ -public class BasicLookupStrategyTests extends AbstractBasicLookupStrategyTests { - - private static final BasicLookupStrategyTestsDbHelper DATABASE_HELPER = new BasicLookupStrategyTestsDbHelper(); - - @BeforeAll - public static void createDatabase() throws Exception { - DATABASE_HELPER.createDatabase(); - } - - @AfterAll - public static void dropDatabase() { - DATABASE_HELPER.getDataSource().destroy(); - } - - @Override - public JdbcTemplate getJdbcTemplate() { - return DATABASE_HELPER.getJdbcTemplate(); - } - - @Override - public DataSource getDataSource() { - return DATABASE_HELPER.getDataSource(); - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTestsDbHelper.java b/acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTestsDbHelper.java deleted file mode 100644 index d57d1f18950..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyTestsDbHelper.java +++ /dev/null @@ -1,78 +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.jdbc; - -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.SingleConnectionDataSource; -import org.springframework.util.FileCopyUtils; - -/** - * Helper class to initialize the database for BasicLookupStrategyTests. - * - * @author Andrei Stefan - * @author Paul Wheeler - */ -public class BasicLookupStrategyTestsDbHelper { - - private static final String ACL_SCHEMA_SQL_FILE = "createAclSchema.sql"; - - private static final String ACL_SCHEMA_SQL_FILE_WITH_ACL_CLASS_ID = "createAclSchemaWithAclClassIdType.sql"; - - private SingleConnectionDataSource dataSource; - - private JdbcTemplate jdbcTemplate; - - private boolean withAclClassIdType; - - public BasicLookupStrategyTestsDbHelper() { - } - - public BasicLookupStrategyTestsDbHelper(boolean withAclClassIdType) { - this.withAclClassIdType = withAclClassIdType; - } - - public void createDatabase() throws Exception { - // Use a different connection url so the tests can run in parallel - String connectionUrl; - String sqlClassPathResource; - if (!this.withAclClassIdType) { - connectionUrl = "jdbc:hsqldb:mem:lookupstrategytest"; - sqlClassPathResource = ACL_SCHEMA_SQL_FILE; - } - else { - connectionUrl = "jdbc:hsqldb:mem:lookupstrategytestWithAclClassIdType"; - sqlClassPathResource = ACL_SCHEMA_SQL_FILE_WITH_ACL_CLASS_ID; - } - this.dataSource = new SingleConnectionDataSource(connectionUrl, "sa", "", true); - this.dataSource.setDriverClassName("org.hsqldb.jdbcDriver"); - this.jdbcTemplate = new JdbcTemplate(this.dataSource); - Resource resource = new ClassPathResource(sqlClassPathResource); - String sql = new String(FileCopyUtils.copyToByteArray(resource.getInputStream())); - this.jdbcTemplate.execute(sql); - } - - public JdbcTemplate getJdbcTemplate() { - return this.jdbcTemplate; - } - - public SingleConnectionDataSource getDataSource() { - return this.dataSource; - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyWithAclClassTypeTests.java b/acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyWithAclClassTypeTests.java deleted file mode 100644 index 94e7d169228..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/jdbc/BasicLookupStrategyWithAclClassTypeTests.java +++ /dev/null @@ -1,122 +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.jdbc; - -import java.util.Arrays; -import java.util.Map; - -import javax.sql.DataSource; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.core.convert.ConversionFailedException; -import org.springframework.core.convert.support.DefaultConversionService; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.security.acls.domain.ConsoleAuditLogger; -import org.springframework.security.acls.domain.DefaultPermissionFactory; -import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy; -import org.springframework.security.acls.domain.ObjectIdentityImpl; -import org.springframework.security.acls.model.Acl; -import org.springframework.security.acls.model.ObjectIdentity; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests {@link BasicLookupStrategy} with Acl Class type id set to UUID. - * - * @author Paul Wheeler - */ -public class BasicLookupStrategyWithAclClassTypeTests extends AbstractBasicLookupStrategyTests { - - private static final BasicLookupStrategyTestsDbHelper DATABASE_HELPER = new BasicLookupStrategyTestsDbHelper(true); - - private BasicLookupStrategy uuidEnabledStrategy; - - @Override - public JdbcTemplate getJdbcTemplate() { - return DATABASE_HELPER.getJdbcTemplate(); - } - - @Override - public DataSource getDataSource() { - return DATABASE_HELPER.getDataSource(); - } - - @BeforeAll - public static void createDatabase() throws Exception { - DATABASE_HELPER.createDatabase(); - } - - @AfterAll - public static void dropDatabase() { - DATABASE_HELPER.getDataSource().destroy(); - } - - @Override - @BeforeEach - public void initializeBeans() { - super.initializeBeans(); - this.uuidEnabledStrategy = new BasicLookupStrategy(getDataSource(), aclCache(), aclAuthStrategy(), - new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger())); - this.uuidEnabledStrategy.setPermissionFactory(new DefaultPermissionFactory()); - this.uuidEnabledStrategy.setAclClassIdSupported(true); - this.uuidEnabledStrategy.setConversionService(new DefaultConversionService()); - } - - @BeforeEach - public void populateDatabaseForAclClassTypeTests() { - String query = "INSERT INTO acl_class(ID,CLASS,CLASS_ID_TYPE) VALUES (3,'" + TARGET_CLASS_WITH_UUID - + "', 'java.util.UUID');" - + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (4,3,'" - + OBJECT_IDENTITY_UUID.toString() + "',null,1,1);" - + "INSERT INTO acl_object_identity(ID,OBJECT_ID_CLASS,OBJECT_ID_IDENTITY,PARENT_OBJECT,OWNER_SID,ENTRIES_INHERITING) VALUES (5,3,'" - + OBJECT_IDENTITY_LONG_AS_UUID + "',null,1,1);" - + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (5,4,0,1,8,0,0,0);" - + "INSERT INTO acl_entry(ID,ACL_OBJECT_IDENTITY,ACE_ORDER,SID,MASK,GRANTING,AUDIT_SUCCESS,AUDIT_FAILURE) VALUES (6,5,0,1,8,0,0,0);"; - DATABASE_HELPER.getJdbcTemplate().execute(query); - } - - @Test - public void testReadObjectIdentityUsingUuidType() { - ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, OBJECT_IDENTITY_UUID); - Map foundAcls = this.uuidEnabledStrategy.readAclsById(Arrays.asList(oid), - Arrays.asList(BEN_SID)); - assertThat(foundAcls).hasSize(1); - assertThat(foundAcls.get(oid)).isNotNull(); - } - - @Test - public void testReadObjectIdentityUsingLongTypeWithConversionServiceEnabled() { - ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS, 100L); - Map foundAcls = this.uuidEnabledStrategy.readAclsById(Arrays.asList(oid), - Arrays.asList(BEN_SID)); - assertThat(foundAcls).hasSize(1); - assertThat(foundAcls.get(oid)).isNotNull(); - } - - @Test - public void testReadObjectIdentityUsingNonUuidInDatabase() { - ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, OBJECT_IDENTITY_LONG_AS_UUID); - assertThatExceptionOfType(ConversionFailedException.class) - .isThrownBy(() -> this.uuidEnabledStrategy.readAclsById(Arrays.asList(oid), Arrays.asList(BEN_SID))); - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/jdbc/DatabaseSeeder.java b/acl/src/test/java/org/springframework/security/acls/jdbc/DatabaseSeeder.java deleted file mode 100644 index eca0b5d635c..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/jdbc/DatabaseSeeder.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited - * - * 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.jdbc; - -import java.io.IOException; - -import javax.sql.DataSource; - -import org.springframework.core.io.Resource; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.util.Assert; -import org.springframework.util.FileCopyUtils; - -/** - * Seeds the database for {@link JdbcMutableAclServiceTests}. - * - * @author Ben Alex - */ -public class DatabaseSeeder { - - public DatabaseSeeder(DataSource dataSource, Resource resource) throws IOException { - Assert.notNull(dataSource, "dataSource required"); - Assert.notNull(resource, "resource required"); - JdbcTemplate template = new JdbcTemplate(dataSource); - String sql = new String(FileCopyUtils.copyToByteArray(resource.getInputStream())); - template.execute(sql); - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/jdbc/JdbcAclServiceTests.java b/acl/src/test/java/org/springframework/security/acls/jdbc/JdbcAclServiceTests.java deleted file mode 100644 index fac5c2ed378..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/jdbc/JdbcAclServiceTests.java +++ /dev/null @@ -1,222 +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.jdbc; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import javax.sql.DataSource; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.security.acls.domain.ObjectIdentityImpl; -import org.springframework.security.acls.domain.PrincipalSid; -import org.springframework.security.acls.model.Acl; -import org.springframework.security.acls.model.NotFoundException; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.Sid; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -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.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; - -/** - * Unit and Integration tests the ACL JdbcAclService using an in-memory database. - * - * @author Nena Raab - */ -@ExtendWith(MockitoExtension.class) -public class JdbcAclServiceTests { - - private EmbeddedDatabase embeddedDatabase; - - @Mock - private DataSource dataSource; - - @Mock - private LookupStrategy lookupStrategy; - - @Mock - JdbcOperations jdbcOperations; - - private JdbcAclService aclServiceIntegration; - - private JdbcAclService aclService; - - @BeforeEach - public void setUp() { - // @formatter:off - this.embeddedDatabase = new EmbeddedDatabaseBuilder() - .addScript("createAclSchemaWithAclClassIdType.sql") - .addScript("db/sql/test_data_hierarchy.sql") - .build(); - // @formatter:on - - this.aclService = new JdbcAclService(this.jdbcOperations, this.lookupStrategy); - this.aclServiceIntegration = new JdbcAclService(this.embeddedDatabase, this.lookupStrategy); - } - - @AfterEach - public void tearDownEmbeddedDatabase() { - this.embeddedDatabase.shutdown(); - } - - // SEC-1898 - @Test - public void readAclByIdMissingAcl() { - Map result = new HashMap<>(); - given(this.lookupStrategy.readAclsById(anyList(), anyList())).willReturn(result); - ObjectIdentity objectIdentity = new ObjectIdentityImpl(Object.class, 1); - List sids = Arrays.asList(new PrincipalSid("user")); - assertThatExceptionOfType(NotFoundException.class) - .isThrownBy(() -> this.aclService.readAclById(objectIdentity, sids)); - } - - @Test - public void findOneChildren() { - List result = new ArrayList<>(); - result.add(new ObjectIdentityImpl(Object.class, "5577")); - Object[] args = { "1", "org.springframework.security.acls.jdbc.JdbcAclServiceTests$MockLongIdDomainObject" }; - given(this.jdbcOperations.query(anyString(), any(RowMapper.class), eq(args))).willReturn(result); - ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockLongIdDomainObject.class, 1L); - List objectIdentities = this.aclService.findChildren(objectIdentity); - assertThat(objectIdentities).hasSize(1); - assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("5577"); - } - - @Test - public void findNoChildren() { - ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockLongIdDomainObject.class, 1L); - List objectIdentities = this.aclService.findChildren(objectIdentity); - assertThat(objectIdentities).isNull(); - } - - @Test - public void findChildrenWithoutIdType() { - ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockLongIdDomainObject.class, 4711L); - List objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity); - assertThat(objectIdentities).hasSize(1); - assertThat(objectIdentities.get(0).getType()).isEqualTo(MockUntypedIdDomainObject.class.getName()); - assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo(5000L); - } - - @Test - public void findChildrenForUnknownObject() { - ObjectIdentity objectIdentity = new ObjectIdentityImpl(Object.class, 33); - List objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity); - assertThat(objectIdentities).isNull(); - } - - @Test - public void findChildrenOfIdTypeLong() { - ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US-PAL"); - List objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity); - assertThat(objectIdentities).hasSize(2); - assertThat(objectIdentities.get(0).getType()).isEqualTo(MockLongIdDomainObject.class.getName()); - assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo(4711L); - assertThat(objectIdentities.get(1).getType()).isEqualTo(MockLongIdDomainObject.class.getName()); - assertThat(objectIdentities.get(1).getIdentifier()).isEqualTo(4712L); - } - - @Test - public void findChildrenOfIdTypeString() { - ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US"); - this.aclServiceIntegration.setAclClassIdSupported(true); - List objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity); - assertThat(objectIdentities).hasSize(1); - assertThat(objectIdentities.get(0).getType()).isEqualTo("location"); - assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("US-PAL"); - } - - @Test - public void findChildrenOfIdTypeUUID() { - ObjectIdentity objectIdentity = new ObjectIdentityImpl(MockUntypedIdDomainObject.class, 5000L); - this.aclServiceIntegration.setAclClassIdSupported(true); - List objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity); - assertThat(objectIdentities).hasSize(1); - assertThat(objectIdentities.get(0).getType()).isEqualTo("costcenter"); - assertThat(objectIdentities.get(0).getIdentifier()) - .isEqualTo(UUID.fromString("25d93b3f-c3aa-4814-9d5e-c7c96ced7762")); - } - - @Test - public void setObjectIdentityGeneratorWhenNullThenThrowsIllegalArgumentException() { - assertThatIllegalArgumentException() - .isThrownBy(() -> this.aclServiceIntegration.setObjectIdentityGenerator(null)) - .withMessage("objectIdentityGenerator cannot be null"); - } - - @Test - public void findChildrenWhenObjectIdentityGeneratorSetThenUsed() { - this.aclServiceIntegration - .setObjectIdentityGenerator((id, type) -> new ObjectIdentityImpl(type, "prefix:" + id)); - - ObjectIdentity objectIdentity = new ObjectIdentityImpl("location", "US"); - this.aclServiceIntegration.setAclClassIdSupported(true); - List objectIdentities = this.aclServiceIntegration.findChildren(objectIdentity); - assertThat(objectIdentities).hasSize(1); - assertThat(objectIdentities.get(0).getType()).isEqualTo("location"); - assertThat(objectIdentities.get(0).getIdentifier()).isEqualTo("prefix:US-PAL"); - } - - class MockLongIdDomainObject { - - private Object id; - - Object getId() { - return this.id; - } - - void setId(Object id) { - this.id = id; - } - - } - - class MockUntypedIdDomainObject { - - private Object id; - - Object getId() { - return this.id; - } - - void setId(Object id) { - this.id = id; - } - - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/jdbc/JdbcMutableAclServiceTests.java b/acl/src/test/java/org/springframework/security/acls/jdbc/JdbcMutableAclServiceTests.java deleted file mode 100644 index 76e5a10ec9a..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/jdbc/JdbcMutableAclServiceTests.java +++ /dev/null @@ -1,497 +0,0 @@ -/* - * Copyright 2004, 2005, 2006, 2017 Acegi Technology Pty Limited - * - * 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.jdbc; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import javax.sql.DataSource; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.io.ClassPathResource; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.security.acls.TargetObject; -import org.springframework.security.acls.domain.AclImpl; -import org.springframework.security.acls.domain.BasePermission; -import org.springframework.security.acls.domain.CumulativePermission; -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.AclCache; -import org.springframework.security.acls.model.AlreadyExistsException; -import org.springframework.security.acls.model.ChildrenExistException; -import org.springframework.security.acls.model.MutableAcl; -import org.springframework.security.acls.model.NotFoundException; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.Permission; -import org.springframework.security.acls.model.Sid; -import org.springframework.security.acls.sid.CustomSid; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.context.transaction.AfterTransaction; -import org.springframework.test.context.transaction.BeforeTransaction; -import org.springframework.transaction.annotation.Transactional; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -/** - * Integration tests the ACL system using an in-memory database. - * - * @author Ben Alex - * @author Andrei Stefan - */ -@Transactional -@ExtendWith(SpringExtension.class) -@ContextConfiguration(locations = { "/jdbcMutableAclServiceTests-context.xml" }) -public class JdbcMutableAclServiceTests { - - private static final String TARGET_CLASS = TargetObject.class.getName(); - - private final Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_ADMINISTRATOR"); - - public static final String SELECT_ALL_CLASSES = "SELECT * FROM acl_class WHERE class = ?"; - - private final ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, 100L); - - private final ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS, 101L); - - private final ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, 102L); - - @Autowired - private JdbcMutableAclService jdbcMutableAclService; - - @Autowired - private AclCache aclCache; - - @Autowired - private LookupStrategy lookupStrategy; - - @Autowired - private DataSource dataSource; - - @Autowired - private JdbcTemplate jdbcTemplate; - - protected String getSqlClassPathResource() { - return "createAclSchema.sql"; - } - - protected ObjectIdentity getTopParentOid() { - return this.topParentOid; - } - - protected ObjectIdentity getMiddleParentOid() { - return this.middleParentOid; - } - - protected ObjectIdentity getChildOid() { - return this.childOid; - } - - protected String getTargetClass() { - return TARGET_CLASS; - } - - @BeforeTransaction - public void createTables() throws Exception { - try { - new DatabaseSeeder(this.dataSource, new ClassPathResource(getSqlClassPathResource())); - // new DatabaseSeeder(dataSource, new - // ClassPathResource("createAclSchemaPostgres.sql")); - } - catch (Exception ex) { - ex.printStackTrace(); - throw ex; - } - } - - @AfterTransaction - public void clearContextAndData() { - SecurityContextHolder.clearContext(); - this.jdbcTemplate.execute("drop table acl_entry"); - this.jdbcTemplate.execute("drop table acl_object_identity"); - this.jdbcTemplate.execute("drop table acl_class"); - this.jdbcTemplate.execute("drop table acl_sid"); - this.aclCache.clearCache(); - } - - @Test - @Transactional - public void testLifecycle() { - SecurityContextHolder.getContext().setAuthentication(this.auth); - MutableAcl topParent = this.jdbcMutableAclService.createAcl(getTopParentOid()); - MutableAcl middleParent = this.jdbcMutableAclService.createAcl(getMiddleParentOid()); - MutableAcl child = this.jdbcMutableAclService.createAcl(getChildOid()); - // Specify the inheritance hierarchy - middleParent.setParent(topParent); - child.setParent(middleParent); - // Now let's add a couple of permissions - topParent.insertAce(0, BasePermission.READ, new PrincipalSid(this.auth), true); - topParent.insertAce(1, BasePermission.WRITE, new PrincipalSid(this.auth), false); - middleParent.insertAce(0, BasePermission.DELETE, new PrincipalSid(this.auth), true); - child.insertAce(0, BasePermission.DELETE, new PrincipalSid(this.auth), false); - // Explicitly save the changed ACL - this.jdbcMutableAclService.updateAcl(topParent); - this.jdbcMutableAclService.updateAcl(middleParent); - this.jdbcMutableAclService.updateAcl(child); - // Let's check if we can read them back correctly - Map map = this.jdbcMutableAclService - .readAclsById(Arrays.asList(getTopParentOid(), getMiddleParentOid(), getChildOid())); - assertThat(map).hasSize(3); - // Get the retrieved versions - MutableAcl retrievedTopParent = (MutableAcl) map.get(getTopParentOid()); - MutableAcl retrievedMiddleParent = (MutableAcl) map.get(getMiddleParentOid()); - MutableAcl retrievedChild = (MutableAcl) map.get(getChildOid()); - // Check the retrieved versions has IDs - assertThat(retrievedTopParent.getId()).isNotNull(); - assertThat(retrievedMiddleParent.getId()).isNotNull(); - assertThat(retrievedChild.getId()).isNotNull(); - // Check their parents were correctly persisted - assertThat(retrievedTopParent.getParentAcl()).isNull(); - assertThat(retrievedMiddleParent.getParentAcl().getObjectIdentity()).isEqualTo(getTopParentOid()); - assertThat(retrievedChild.getParentAcl().getObjectIdentity()).isEqualTo(getMiddleParentOid()); - // Check their ACEs were correctly persisted - assertThat(retrievedTopParent.getEntries()).hasSize(2); - assertThat(retrievedMiddleParent.getEntries()).hasSize(1); - assertThat(retrievedChild.getEntries()).hasSize(1); - // Check the retrieved rights are correct - List read = Arrays.asList(BasePermission.READ); - List write = Arrays.asList(BasePermission.WRITE); - List delete = Arrays.asList(BasePermission.DELETE); - List pSid = Arrays.asList((Sid) new PrincipalSid(this.auth)); - assertThat(retrievedTopParent.isGranted(read, pSid, false)).isTrue(); - assertThat(retrievedTopParent.isGranted(write, pSid, false)).isFalse(); - assertThat(retrievedMiddleParent.isGranted(delete, pSid, false)).isTrue(); - assertThat(retrievedChild.isGranted(delete, pSid, false)).isFalse(); - assertThatExceptionOfType(NotFoundException.class) - .isThrownBy(() -> retrievedChild.isGranted(Arrays.asList(BasePermission.ADMINISTRATION), pSid, false)); - // Now check the inherited rights (when not explicitly overridden) also look OK - assertThat(retrievedChild.isGranted(read, pSid, false)).isTrue(); - assertThat(retrievedChild.isGranted(write, pSid, false)).isFalse(); - assertThat(retrievedChild.isGranted(delete, pSid, false)).isFalse(); - // Next change the child so it doesn't inherit permissions from above - retrievedChild.setEntriesInheriting(false); - this.jdbcMutableAclService.updateAcl(retrievedChild); - MutableAcl nonInheritingChild = (MutableAcl) this.jdbcMutableAclService.readAclById(getChildOid()); - assertThat(nonInheritingChild.isEntriesInheriting()).isFalse(); - // Check the child permissions no longer inherit - assertThat(nonInheritingChild.isGranted(delete, pSid, true)).isFalse(); - assertThatExceptionOfType(NotFoundException.class) - .isThrownBy(() -> nonInheritingChild.isGranted(read, pSid, true)); - assertThatExceptionOfType(NotFoundException.class) - .isThrownBy(() -> nonInheritingChild.isGranted(write, pSid, true)); - // Let's add an identical permission to the child, but it'll appear AFTER the - // current permission, so has no impact - nonInheritingChild.insertAce(1, BasePermission.DELETE, new PrincipalSid(this.auth), true); - // Let's also add another permission to the child - nonInheritingChild.insertAce(2, BasePermission.CREATE, new PrincipalSid(this.auth), true); - // Save the changed child - this.jdbcMutableAclService.updateAcl(nonInheritingChild); - MutableAcl retrievedNonInheritingChild = (MutableAcl) this.jdbcMutableAclService.readAclById(getChildOid()); - assertThat(retrievedNonInheritingChild.getEntries()).hasSize(3); - // Output permissions - for (int i = 0; i < retrievedNonInheritingChild.getEntries().size(); i++) { - System.out.println(retrievedNonInheritingChild.getEntries().get(i)); - } - // Check the permissions are as they should be - assertThat(retrievedNonInheritingChild.isGranted(delete, pSid, true)).isFalse(); // as - // earlier - // permission - // overrode - assertThat(retrievedNonInheritingChild.isGranted(Arrays.asList(BasePermission.CREATE), pSid, true)).isTrue(); - // Now check the first ACE (index 0) really is DELETE for our Sid and is - // non-granting - AccessControlEntry entry = retrievedNonInheritingChild.getEntries().get(0); - assertThat(entry.getPermission().getMask()).isEqualTo(BasePermission.DELETE.getMask()); - assertThat(entry.getSid()).isEqualTo(new PrincipalSid(this.auth)); - assertThat(entry.isGranting()).isFalse(); - assertThat(entry.getId()).isNotNull(); - // Now delete that first ACE - retrievedNonInheritingChild.deleteAce(0); - // Save and check it worked - MutableAcl savedChild = this.jdbcMutableAclService.updateAcl(retrievedNonInheritingChild); - assertThat(savedChild.getEntries()).hasSize(2); - assertThat(savedChild.isGranted(delete, pSid, false)).isTrue(); - SecurityContextHolder.clearContext(); - } - - /** - * Test method that demonstrates eviction failure from cache - SEC-676 - */ - @Test - @Transactional - public void deleteAclAlsoDeletesChildren() { - SecurityContextHolder.getContext().setAuthentication(this.auth); - this.jdbcMutableAclService.createAcl(getTopParentOid()); - MutableAcl middleParent = this.jdbcMutableAclService.createAcl(getMiddleParentOid()); - MutableAcl child = this.jdbcMutableAclService.createAcl(getChildOid()); - child.setParent(middleParent); - this.jdbcMutableAclService.updateAcl(middleParent); - this.jdbcMutableAclService.updateAcl(child); - // Check the childOid really is a child of middleParentOid - Acl childAcl = this.jdbcMutableAclService.readAclById(getChildOid()); - assertThat(childAcl.getParentAcl().getObjectIdentity()).isEqualTo(getMiddleParentOid()); - // Delete the mid-parent and test if the child was deleted, as well - this.jdbcMutableAclService.deleteAcl(getMiddleParentOid(), true); - assertThatExceptionOfType(NotFoundException.class) - .isThrownBy(() -> this.jdbcMutableAclService.readAclById(getMiddleParentOid())); - assertThatExceptionOfType(NotFoundException.class) - .isThrownBy(() -> this.jdbcMutableAclService.readAclById(getChildOid())); - Acl acl = this.jdbcMutableAclService.readAclById(getTopParentOid()); - assertThat(acl).isNotNull(); - assertThat(getTopParentOid()).isEqualTo(acl.getObjectIdentity()); - } - - @Test - public void constructorRejectsNullParameters() { - assertThatIllegalArgumentException() - .isThrownBy(() -> new JdbcMutableAclService(null, this.lookupStrategy, this.aclCache)); - assertThatIllegalArgumentException() - .isThrownBy(() -> new JdbcMutableAclService(this.dataSource, null, this.aclCache)); - assertThatIllegalArgumentException() - .isThrownBy(() -> new JdbcMutableAclService(this.dataSource, this.lookupStrategy, null)); - } - - @Test - public void createAclRejectsNullParameter() { - assertThatIllegalArgumentException().isThrownBy(() -> this.jdbcMutableAclService.createAcl(null)); - } - - @Test - @Transactional - public void createAclForADuplicateDomainObject() { - SecurityContextHolder.getContext().setAuthentication(this.auth); - ObjectIdentity duplicateOid = new ObjectIdentityImpl(TARGET_CLASS, 100L); - this.jdbcMutableAclService.createAcl(duplicateOid); - // Try to add the same object second time - assertThatExceptionOfType(AlreadyExistsException.class) - .isThrownBy(() -> this.jdbcMutableAclService.createAcl(duplicateOid)); - } - - @Test - @Transactional - public void deleteAclRejectsNullParameters() { - assertThatIllegalArgumentException().isThrownBy(() -> this.jdbcMutableAclService.deleteAcl(null, true)); - } - - @Test - @Transactional - public void deleteAclWithChildrenThrowsException() { - SecurityContextHolder.getContext().setAuthentication(this.auth); - MutableAcl parent = this.jdbcMutableAclService.createAcl(getTopParentOid()); - MutableAcl child = this.jdbcMutableAclService.createAcl(getMiddleParentOid()); - // Specify the inheritance hierarchy - child.setParent(parent); - this.jdbcMutableAclService.updateAcl(child); - // switch on FK - this.jdbcMutableAclService.setForeignKeysInDatabase(false); - try { - // checking in the class, not database - assertThatExceptionOfType(ChildrenExistException.class) - .isThrownBy(() -> this.jdbcMutableAclService.deleteAcl(getTopParentOid(), false)); - } - finally { - // restore to the default - this.jdbcMutableAclService.setForeignKeysInDatabase(true); - } - } - - @Test - @Transactional - public void deleteAclRemovesRowsFromDatabase() { - SecurityContextHolder.getContext().setAuthentication(this.auth); - MutableAcl child = this.jdbcMutableAclService.createAcl(getChildOid()); - child.insertAce(0, BasePermission.DELETE, new PrincipalSid(this.auth), false); - this.jdbcMutableAclService.updateAcl(child); - // Remove the child and check all related database rows were removed accordingly - this.jdbcMutableAclService.deleteAcl(getChildOid(), false); - assertThat(this.jdbcTemplate.queryForList(SELECT_ALL_CLASSES, new Object[] { getTargetClass() })).hasSize(1); - assertThat(this.jdbcTemplate.queryForList("select * from acl_object_identity")).isEmpty(); - assertThat(this.jdbcTemplate.queryForList("select * from acl_entry")).isEmpty(); - // Check the cache - assertThat(this.aclCache.getFromCache(getChildOid())).isNull(); - assertThat(this.aclCache.getFromCache(102L)).isNull(); - } - - /** SEC-1107 */ - @Test - @Transactional - public void identityWithIntegerIdIsSupportedByCreateAcl() { - SecurityContextHolder.getContext().setAuthentication(this.auth); - ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS, 101); - this.jdbcMutableAclService.createAcl(oid); - assertThat(this.jdbcMutableAclService.readAclById(new ObjectIdentityImpl(TARGET_CLASS, 101L))).isNotNull(); - } - - @Test - @Transactional - public void createAclWhenCustomSecurityContextHolderStrategyThenUses() { - SecurityContextHolderStrategy securityContextHolderStrategy = mock(SecurityContextHolderStrategy.class); - SecurityContext context = new SecurityContextImpl(this.auth); - given(securityContextHolderStrategy.getContext()).willReturn(context); - JdbcMutableAclService service = new JdbcMutableAclService(this.dataSource, this.lookupStrategy, this.aclCache); - service.setSecurityContextHolderStrategy(securityContextHolderStrategy); - ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS, 101); - service.createAcl(oid); - verify(securityContextHolderStrategy).getContext(); - } - - /** - * SEC-655 - */ - @Test - @Transactional - public void childrenAreClearedFromCacheWhenParentIsUpdated() { - Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_ADMINISTRATOR"); - auth.setAuthenticated(true); - SecurityContextHolder.getContext().setAuthentication(auth); - ObjectIdentity parentOid = new ObjectIdentityImpl(TARGET_CLASS, 104L); - ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS, 105L); - MutableAcl parent = this.jdbcMutableAclService.createAcl(parentOid); - MutableAcl child = this.jdbcMutableAclService.createAcl(childOid); - child.setParent(parent); - this.jdbcMutableAclService.updateAcl(child); - parent = (AclImpl) this.jdbcMutableAclService.readAclById(parentOid); - parent.insertAce(0, BasePermission.READ, new PrincipalSid("ben"), true); - this.jdbcMutableAclService.updateAcl(parent); - parent = (AclImpl) this.jdbcMutableAclService.readAclById(parentOid); - parent.insertAce(1, BasePermission.READ, new PrincipalSid("scott"), true); - this.jdbcMutableAclService.updateAcl(parent); - child = (MutableAcl) this.jdbcMutableAclService.readAclById(childOid); - parent = (MutableAcl) child.getParentAcl(); - assertThat(parent.getEntries()).hasSize(2) - .withFailMessage("Fails because child has a stale reference to its parent"); - assertThat(parent.getEntries().get(0).getPermission().getMask()).isEqualTo(1); - assertThat(parent.getEntries().get(0).getSid()).isEqualTo(new PrincipalSid("ben")); - assertThat(parent.getEntries().get(1).getPermission().getMask()).isEqualTo(1); - assertThat(parent.getEntries().get(1).getSid()).isEqualTo(new PrincipalSid("scott")); - } - - /** - * SEC-655 - */ - @Test - @Transactional - public void childrenAreClearedFromCacheWhenParentisUpdated2() { - Authentication auth = new TestingAuthenticationToken("system", "secret", "ROLE_IGNORED"); - SecurityContextHolder.getContext().setAuthentication(auth); - ObjectIdentityImpl rootObject = new ObjectIdentityImpl(TARGET_CLASS, 1L); - MutableAcl parent = this.jdbcMutableAclService.createAcl(rootObject); - MutableAcl child = this.jdbcMutableAclService.createAcl(new ObjectIdentityImpl(TARGET_CLASS, 2L)); - child.setParent(parent); - this.jdbcMutableAclService.updateAcl(child); - parent.insertAce(0, BasePermission.ADMINISTRATION, new GrantedAuthoritySid("ROLE_ADMINISTRATOR"), true); - this.jdbcMutableAclService.updateAcl(parent); - parent.insertAce(1, BasePermission.DELETE, new PrincipalSid("terry"), true); - this.jdbcMutableAclService.updateAcl(parent); - child = (MutableAcl) this.jdbcMutableAclService.readAclById(new ObjectIdentityImpl(TARGET_CLASS, 2L)); - parent = (MutableAcl) child.getParentAcl(); - assertThat(parent.getEntries()).hasSize(2); - assertThat(parent.getEntries().get(0).getPermission().getMask()).isEqualTo(16); - assertThat(parent.getEntries().get(0).getSid()).isEqualTo(new GrantedAuthoritySid("ROLE_ADMINISTRATOR")); - assertThat(parent.getEntries().get(1).getPermission().getMask()).isEqualTo(8); - assertThat(parent.getEntries().get(1).getSid()).isEqualTo(new PrincipalSid("terry")); - } - - @Test - @Transactional - public void cumulativePermissions() { - Authentication auth = new TestingAuthenticationToken("ben", "ignored", "ROLE_ADMINISTRATOR"); - auth.setAuthenticated(true); - SecurityContextHolder.getContext().setAuthentication(auth); - ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, 110L); - MutableAcl topParent = this.jdbcMutableAclService.createAcl(topParentOid); - // Add an ACE permission entry - Permission cm = new CumulativePermission().set(BasePermission.READ).set(BasePermission.ADMINISTRATION); - assertThat(cm.getMask()).isEqualTo(17); - Sid benSid = new PrincipalSid(auth); - topParent.insertAce(0, cm, benSid, true); - assertThat(topParent.getEntries()).hasSize(1); - // Explicitly save the changed ACL - topParent = this.jdbcMutableAclService.updateAcl(topParent); - // Check the mask was retrieved correctly - assertThat(topParent.getEntries().get(0).getPermission().getMask()).isEqualTo(17); - assertThat(topParent.isGranted(Arrays.asList(cm), Arrays.asList(benSid), true)).isTrue(); - SecurityContextHolder.clearContext(); - } - - @Test - public void testProcessingCustomSid() { - CustomJdbcMutableAclService customJdbcMutableAclService = spy( - new CustomJdbcMutableAclService(this.dataSource, this.lookupStrategy, this.aclCache)); - CustomSid customSid = new CustomSid("Custom sid"); - given(customJdbcMutableAclService.createOrRetrieveSidPrimaryKey("Custom sid", false, false)).willReturn(1L); - Long result = customJdbcMutableAclService.createOrRetrieveSidPrimaryKey(customSid, false); - assertThat(Long.valueOf(1L)).isEqualTo(result); - } - - protected Authentication getAuth() { - return this.auth; - } - - protected JdbcMutableAclService getJdbcMutableAclService() { - return this.jdbcMutableAclService; - } - - /** - * This class needed to show how to extend {@link JdbcMutableAclService} for - * processing custom {@link Sid} implementations - */ - private class CustomJdbcMutableAclService extends JdbcMutableAclService { - - CustomJdbcMutableAclService(DataSource dataSource, LookupStrategy lookupStrategy, AclCache aclCache) { - super(dataSource, lookupStrategy, aclCache); - } - - @Override - protected Long createOrRetrieveSidPrimaryKey(Sid sid, boolean allowCreate) { - String sidName; - boolean isPrincipal = false; - if (sid instanceof CustomSid) { - sidName = ((CustomSid) sid).getSid(); - } - else if (sid instanceof GrantedAuthoritySid) { - sidName = ((GrantedAuthoritySid) sid).getGrantedAuthority(); - } - else { - sidName = ((PrincipalSid) sid).getPrincipal(); - isPrincipal = true; - } - return createOrRetrieveSidPrimaryKey(sidName, isPrincipal, allowCreate); - } - - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/jdbc/JdbcMutableAclServiceTestsWithAclClassId.java b/acl/src/test/java/org/springframework/security/acls/jdbc/JdbcMutableAclServiceTestsWithAclClassId.java deleted file mode 100644 index 3e04718cd6c..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/jdbc/JdbcMutableAclServiceTestsWithAclClassId.java +++ /dev/null @@ -1,85 +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.jdbc; - -import java.util.UUID; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.acls.TargetObjectWithUUID; -import org.springframework.security.acls.domain.ObjectIdentityImpl; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.transaction.annotation.Transactional; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Integration tests the ACL system using ACL class id type of UUID and using an in-memory - * database. - * - * @author Paul Wheeler - */ -@ContextConfiguration(locations = { "/jdbcMutableAclServiceTestsWithAclClass-context.xml" }) -public class JdbcMutableAclServiceTestsWithAclClassId extends JdbcMutableAclServiceTests { - - private static final String TARGET_CLASS_WITH_UUID = TargetObjectWithUUID.class.getName(); - - private final ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, UUID.randomUUID()); - - private final ObjectIdentity middleParentOid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, UUID.randomUUID()); - - private final ObjectIdentity childOid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, UUID.randomUUID()); - - @Override - protected String getSqlClassPathResource() { - return "createAclSchemaWithAclClassIdType.sql"; - } - - @Override - protected ObjectIdentity getTopParentOid() { - return this.topParentOid; - } - - @Override - protected ObjectIdentity getMiddleParentOid() { - return this.middleParentOid; - } - - @Override - protected ObjectIdentity getChildOid() { - return this.childOid; - } - - @Override - protected String getTargetClass() { - return TARGET_CLASS_WITH_UUID; - } - - @Test - @Transactional - public void identityWithUuidIdIsSupportedByCreateAcl() { - SecurityContextHolder.getContext().setAuthentication(getAuth()); - UUID id = UUID.randomUUID(); - ObjectIdentity oid = new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, id); - getJdbcMutableAclService().createAcl(oid); - assertThat(getJdbcMutableAclService().readAclById(new ObjectIdentityImpl(TARGET_CLASS_WITH_UUID, id))) - .isNotNull(); - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/jdbc/SpringCacheBasedAclCacheTests.java b/acl/src/test/java/org/springframework/security/acls/jdbc/SpringCacheBasedAclCacheTests.java deleted file mode 100644 index f14f8b32483..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/jdbc/SpringCacheBasedAclCacheTests.java +++ /dev/null @@ -1,156 +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.jdbc; - -import java.util.Map; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; - -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; -import org.springframework.cache.concurrent.ConcurrentMapCacheManager; -import org.springframework.security.acls.domain.AclAuthorizationStrategy; -import org.springframework.security.acls.domain.AclAuthorizationStrategyImpl; -import org.springframework.security.acls.domain.AclImpl; -import org.springframework.security.acls.domain.AuditLogger; -import org.springframework.security.acls.domain.ConsoleAuditLogger; -import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy; -import org.springframework.security.acls.domain.ObjectIdentityImpl; -import org.springframework.security.acls.domain.SpringCacheBasedAclCache; -import org.springframework.security.acls.model.MutableAcl; -import org.springframework.security.acls.model.ObjectIdentity; -import org.springframework.security.acls.model.PermissionGrantingStrategy; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.util.FieldUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; - -/** - * Tests {@link org.springframework.security.acls.domain.SpringCacheBasedAclCache} - * - * @author Marten Deinum - */ -public class SpringCacheBasedAclCacheTests { - - private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject"; - - private static CacheManager cacheManager; - - @BeforeAll - public static void initCacheManaer() { - cacheManager = new ConcurrentMapCacheManager(); - // Use disk caching immediately (to test for serialization issue reported in - // SEC-527) - cacheManager.getCache("springcasebasedacltests"); - } - - @AfterEach - public void clearContext() { - SecurityContextHolder.clearContext(); - } - - private Cache getCache() { - Cache cache = cacheManager.getCache("springcasebasedacltests"); - cache.clear(); - return cache; - } - - @Test - public void constructorRejectsNullParameters() { - assertThatIllegalArgumentException().isThrownBy(() -> new SpringCacheBasedAclCache(null, null, null)); - } - - @SuppressWarnings("rawtypes") - @Test - public void cacheOperationsAclWithoutParent() { - Cache cache = getCache(); - Map realCache = (Map) cache.getNativeCache(); - ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100L); - AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl( - new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"), - new SimpleGrantedAuthority("ROLE_GENERAL")); - AuditLogger auditLogger = new ConsoleAuditLogger(); - PermissionGrantingStrategy permissionGrantingStrategy = new DefaultPermissionGrantingStrategy(auditLogger); - SpringCacheBasedAclCache myCache = new SpringCacheBasedAclCache(cache, permissionGrantingStrategy, - aclAuthorizationStrategy); - MutableAcl acl = new AclImpl(identity, 1L, aclAuthorizationStrategy, auditLogger); - assertThat(realCache).isEmpty(); - myCache.putInCache(acl); - // Check we can get from cache the same objects we put in - assertThat(acl).isEqualTo(myCache.getFromCache(1L)); - assertThat(acl).isEqualTo(myCache.getFromCache(identity)); - // Put another object in cache - ObjectIdentity identity2 = new ObjectIdentityImpl(TARGET_CLASS, 101L); - MutableAcl acl2 = new AclImpl(identity2, 2L, aclAuthorizationStrategy, new ConsoleAuditLogger()); - myCache.putInCache(acl2); - // Try to evict an entry that doesn't exist - myCache.evictFromCache(3L); - myCache.evictFromCache(new ObjectIdentityImpl(TARGET_CLASS, 102L)); - assertThat(realCache).hasSize(4); - myCache.evictFromCache(1L); - assertThat(realCache).hasSize(2); - // Check the second object inserted - assertThat(acl2).isEqualTo(myCache.getFromCache(2L)); - assertThat(acl2).isEqualTo(myCache.getFromCache(identity2)); - myCache.evictFromCache(identity2); - assertThat(realCache).isEmpty(); - } - - @SuppressWarnings("rawtypes") - @Test - public void cacheOperationsAclWithParent() throws Exception { - Cache cache = getCache(); - Map realCache = (Map) cache.getNativeCache(); - Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_GENERAL"); - auth.setAuthenticated(true); - SecurityContextHolder.getContext().setAuthentication(auth); - ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 1L); - ObjectIdentity identityParent = new ObjectIdentityImpl(TARGET_CLASS, 2L); - AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl( - new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"), - new SimpleGrantedAuthority("ROLE_GENERAL")); - AuditLogger auditLogger = new ConsoleAuditLogger(); - PermissionGrantingStrategy permissionGrantingStrategy = new DefaultPermissionGrantingStrategy(auditLogger); - SpringCacheBasedAclCache myCache = new SpringCacheBasedAclCache(cache, permissionGrantingStrategy, - aclAuthorizationStrategy); - MutableAcl acl = new AclImpl(identity, 1L, aclAuthorizationStrategy, auditLogger); - MutableAcl parentAcl = new AclImpl(identityParent, 2L, aclAuthorizationStrategy, auditLogger); - acl.setParent(parentAcl); - assertThat(realCache).isEmpty(); - myCache.putInCache(acl); - assertThat(4).isEqualTo(realCache.size()); - // Check we can get from cache the same objects we put in - AclImpl aclFromCache = (AclImpl) myCache.getFromCache(1L); - assertThat(aclFromCache).isEqualTo(acl); - // SEC-951 check transient fields are set on parent - assertThat(FieldUtils.getFieldValue(aclFromCache.getParentAcl(), "aclAuthorizationStrategy")).isNotNull(); - assertThat(FieldUtils.getFieldValue(aclFromCache.getParentAcl(), "permissionGrantingStrategy")).isNotNull(); - assertThat(myCache.getFromCache(identity)).isEqualTo(acl); - assertThat(FieldUtils.getFieldValue(aclFromCache, "aclAuthorizationStrategy")).isNotNull(); - AclImpl parentAclFromCache = (AclImpl) myCache.getFromCache(2L); - assertThat(parentAclFromCache).isEqualTo(parentAcl); - assertThat(FieldUtils.getFieldValue(parentAclFromCache, "aclAuthorizationStrategy")).isNotNull(); - assertThat(myCache.getFromCache(identityParent)).isEqualTo(parentAcl); - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/sid/CustomSid.java b/acl/src/test/java/org/springframework/security/acls/sid/CustomSid.java deleted file mode 100644 index 0ca2501d04f..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/sid/CustomSid.java +++ /dev/null @@ -1,42 +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.sid; - -import org.springframework.security.acls.model.Sid; - -/** - * This class is example of custom {@link Sid} implementation - * - * @author Mikhail Stryzhonok - */ -public class CustomSid implements Sid { - - private String sid; - - public CustomSid(String sid) { - this.sid = sid; - } - - public String getSid() { - return this.sid; - } - - public void setSid(String sid) { - this.sid = sid; - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/sid/SidRetrievalStrategyTests.java b/acl/src/test/java/org/springframework/security/acls/sid/SidRetrievalStrategyTests.java deleted file mode 100644 index dd7b7af8879..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/sid/SidRetrievalStrategyTests.java +++ /dev/null @@ -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.sid; - -import java.util.List; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.hierarchicalroles.RoleHierarchy; -import org.springframework.security.acls.domain.GrantedAuthoritySid; -import org.springframework.security.acls.domain.PrincipalSid; -import org.springframework.security.acls.domain.SidRetrievalStrategyImpl; -import org.springframework.security.acls.model.Sid; -import org.springframework.security.acls.model.SidRetrievalStrategy; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyCollection; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link SidRetrievalStrategyImpl} - * - * @author Andrei Stefan - * @author Luke Taylor - */ -@SuppressWarnings("unchecked") -public class SidRetrievalStrategyTests { - - Authentication authentication = new TestingAuthenticationToken("scott", "password", "A", "B", "C"); - - @Test - public void correctSidsAreRetrieved() { - SidRetrievalStrategy retrStrategy = new SidRetrievalStrategyImpl(); - List sids = retrStrategy.getSids(this.authentication); - assertThat(sids).isNotNull(); - assertThat(sids).hasSize(4); - assertThat(sids.get(0)).isNotNull(); - assertThat(sids.get(0) instanceof PrincipalSid).isTrue(); - for (int i = 1; i < sids.size(); i++) { - assertThat(sids.get(i) instanceof GrantedAuthoritySid).isTrue(); - } - assertThat(((PrincipalSid) sids.get(0)).getPrincipal()).isEqualTo("scott"); - assertThat(((GrantedAuthoritySid) sids.get(1)).getGrantedAuthority()).isEqualTo("A"); - assertThat(((GrantedAuthoritySid) sids.get(2)).getGrantedAuthority()).isEqualTo("B"); - assertThat(((GrantedAuthoritySid) sids.get(3)).getGrantedAuthority()).isEqualTo("C"); - } - - @Test - public void roleHierarchyIsUsedWhenSet() { - RoleHierarchy rh = mock(RoleHierarchy.class); - List rhAuthorities = AuthorityUtils.createAuthorityList("D"); - given(rh.getReachableGrantedAuthorities(anyCollection())).willReturn(rhAuthorities); - SidRetrievalStrategy strat = new SidRetrievalStrategyImpl(rh); - List sids = strat.getSids(this.authentication); - assertThat(sids).hasSize(2); - assertThat(sids.get(0)).isNotNull(); - assertThat(sids.get(0) instanceof PrincipalSid).isTrue(); - assertThat(((GrantedAuthoritySid) sids.get(1)).getGrantedAuthority()).isEqualTo("D"); - } - -} diff --git a/acl/src/test/java/org/springframework/security/acls/sid/SidTests.java b/acl/src/test/java/org/springframework/security/acls/sid/SidTests.java deleted file mode 100644 index c52e9158e29..00000000000 --- a/acl/src/test/java/org/springframework/security/acls/sid/SidTests.java +++ /dev/null @@ -1,191 +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.sid; - -import java.util.Collection; -import java.util.Collections; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.acls.domain.GrantedAuthoritySid; -import org.springframework.security.acls.domain.PrincipalSid; -import org.springframework.security.acls.model.Sid; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.User; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.assertThatNoException; - -public class SidTests { - - @Test - public void testPrincipalSidConstructorsRequiredFields() { - // Check one String-argument constructor - assertThatIllegalArgumentException().isThrownBy(() -> new PrincipalSid((String) null)); - assertThatIllegalArgumentException().isThrownBy(() -> new PrincipalSid("")); - assertThatNoException().isThrownBy(() -> new PrincipalSid("johndoe")); - // Check one Authentication-argument constructor - assertThatIllegalArgumentException().isThrownBy(() -> new PrincipalSid((Authentication) null)); - assertThatIllegalArgumentException() - .isThrownBy(() -> new PrincipalSid(new TestingAuthenticationToken(null, "password"))); - assertThatNoException() - .isThrownBy(() -> new PrincipalSid(new TestingAuthenticationToken("johndoe", "password"))); - } - - @Test - public void testGrantedAuthoritySidConstructorsRequiredFields() { - // Check one String-argument constructor - assertThatIllegalArgumentException().isThrownBy(() -> new GrantedAuthoritySid((String) null)); - assertThatIllegalArgumentException().isThrownBy(() -> new GrantedAuthoritySid("")); - assertThatNoException().isThrownBy(() -> new GrantedAuthoritySid("ROLE_TEST")); - // Check one GrantedAuthority-argument constructor - assertThatIllegalArgumentException().isThrownBy(() -> new GrantedAuthoritySid((GrantedAuthority) null)); - assertThatIllegalArgumentException() - .isThrownBy(() -> new GrantedAuthoritySid(new SimpleGrantedAuthority(null))); - assertThatNoException().isThrownBy(() -> new GrantedAuthoritySid(new SimpleGrantedAuthority("ROLE_TEST"))); - } - - @Test - public void testPrincipalSidEquals() { - Authentication authentication = new TestingAuthenticationToken("johndoe", "password"); - Sid principalSid = new PrincipalSid(authentication); - assertThat(principalSid.equals(null)).isFalse(); - assertThat(principalSid.equals("DIFFERENT_TYPE_OBJECT")).isFalse(); - assertThat(principalSid.equals(principalSid)).isTrue(); - assertThat(principalSid.equals(new PrincipalSid(authentication))).isTrue(); - assertThat(principalSid.equals(new PrincipalSid(new TestingAuthenticationToken("johndoe", null)))).isTrue(); - assertThat(principalSid.equals(new PrincipalSid(new TestingAuthenticationToken("scott", null)))).isFalse(); - assertThat(principalSid.equals(new PrincipalSid("johndoe"))).isTrue(); - assertThat(principalSid.equals(new PrincipalSid("scott"))).isFalse(); - } - - @Test - public void testGrantedAuthoritySidEquals() { - GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST"); - Sid gaSid = new GrantedAuthoritySid(ga); - assertThat(gaSid.equals(null)).isFalse(); - assertThat(gaSid.equals("DIFFERENT_TYPE_OBJECT")).isFalse(); - assertThat(gaSid.equals(gaSid)).isTrue(); - assertThat(gaSid.equals(new GrantedAuthoritySid(ga))).isTrue(); - assertThat(gaSid.equals(new GrantedAuthoritySid(new SimpleGrantedAuthority("ROLE_TEST")))).isTrue(); - assertThat(gaSid.equals(new GrantedAuthoritySid(new SimpleGrantedAuthority("ROLE_NOT_EQUAL")))).isFalse(); - assertThat(gaSid.equals(new GrantedAuthoritySid("ROLE_TEST"))).isTrue(); - assertThat(gaSid.equals(new GrantedAuthoritySid("ROLE_NOT_EQUAL"))).isFalse(); - } - - @Test - public void testPrincipalSidHashCode() { - Authentication authentication = new TestingAuthenticationToken("johndoe", "password"); - Sid principalSid = new PrincipalSid(authentication); - assertThat(principalSid.hashCode()).isEqualTo("johndoe".hashCode()); - assertThat(principalSid.hashCode()).isEqualTo(new PrincipalSid("johndoe").hashCode()); - assertThat(principalSid.hashCode()).isNotEqualTo(new PrincipalSid("scott").hashCode()); - assertThat(principalSid.hashCode()) - .isNotEqualTo(new PrincipalSid(new TestingAuthenticationToken("scott", "password")).hashCode()); - } - - @Test - public void testGrantedAuthoritySidHashCode() { - GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST"); - Sid gaSid = new GrantedAuthoritySid(ga); - assertThat(gaSid.hashCode()).isEqualTo("ROLE_TEST".hashCode()); - assertThat(gaSid.hashCode()).isEqualTo(new GrantedAuthoritySid("ROLE_TEST").hashCode()); - assertThat(gaSid.hashCode()).isNotEqualTo(new GrantedAuthoritySid("ROLE_TEST_2").hashCode()); - assertThat(gaSid.hashCode()) - .isNotEqualTo(new GrantedAuthoritySid(new SimpleGrantedAuthority("ROLE_TEST_2")).hashCode()); - } - - @Test - public void testGetters() { - Authentication authentication = new TestingAuthenticationToken("johndoe", "password"); - PrincipalSid principalSid = new PrincipalSid(authentication); - GrantedAuthority ga = new SimpleGrantedAuthority("ROLE_TEST"); - GrantedAuthoritySid gaSid = new GrantedAuthoritySid(ga); - assertThat("johndoe").isEqualTo(principalSid.getPrincipal()); - assertThat("scott".equals(principalSid.getPrincipal())).isFalse(); - assertThat("ROLE_TEST").isEqualTo(gaSid.getGrantedAuthority()); - assertThat("ROLE_TEST2".equals(gaSid.getGrantedAuthority())).isFalse(); - } - - @Test - public void getPrincipalWhenPrincipalInstanceOfUserDetailsThenReturnsUsername() { - User user = new User("user", "password", Collections.singletonList(new SimpleGrantedAuthority("ROLE_TEST"))); - Authentication authentication = new TestingAuthenticationToken(user, "password"); - PrincipalSid principalSid = new PrincipalSid(authentication); - assertThat("user").isEqualTo(principalSid.getPrincipal()); - } - - @Test - public void getPrincipalWhenPrincipalNotInstanceOfUserDetailsThenReturnsPrincipalName() { - Authentication authentication = new TestingAuthenticationToken("token", "password"); - PrincipalSid principalSid = new PrincipalSid(authentication); - assertThat("token").isEqualTo(principalSid.getPrincipal()); - } - - @Test - public void getPrincipalWhenCustomAuthenticationPrincipalThenReturnsPrincipalName() { - Authentication authentication = new CustomAuthenticationToken(new CustomToken("token"), null); - PrincipalSid principalSid = new PrincipalSid(authentication); - assertThat("token").isEqualTo(principalSid.getPrincipal()); - } - - static class CustomAuthenticationToken extends AbstractAuthenticationToken { - - private CustomToken principal; - - CustomAuthenticationToken(CustomToken principal, Collection authorities) { - super(authorities); - this.principal = principal; - } - - @Override - public Object getCredentials() { - return null; - } - - @Override - public CustomToken getPrincipal() { - return this.principal; - } - - @Override - public String getName() { - return this.principal.getName(); - } - - } - - static class CustomToken { - - private String name; - - CustomToken(String name) { - this.name = name; - } - - String getName() { - return this.name; - } - - } - -} diff --git a/acl/src/test/resources/db/sql/test_data_hierarchy.sql b/acl/src/test/resources/db/sql/test_data_hierarchy.sql deleted file mode 100644 index a0c41793aee..00000000000 --- a/acl/src/test/resources/db/sql/test_data_hierarchy.sql +++ /dev/null @@ -1,17 +0,0 @@ ---- insert ACL data -INSERT INTO ACL_SID (ID, PRINCIPAL, SID) VALUES - (10, true, 'user'); - -INSERT INTO acl_class (id, class, class_id_type) VALUES - (20,'location','java.lang.String'), - (21,'org.springframework.security.acls.jdbc.JdbcAclServiceTests$MockLongIdDomainObject','java.lang.Long'), - (22,'org.springframework.security.acls.jdbc.JdbcAclServiceTests$MockUntypedIdDomainObject',''), - (23,'costcenter','java.util.UUID'); - -INSERT INTO acl_object_identity (id, object_id_class, object_id_identity, parent_object, owner_sid, entries_inheriting) VALUES - (1,20,'US',NULL,10,false), - (2,20,'US-PAL',1,10,true), - (3,21,'4711',2,10,true), - (4,21,'4712',2,10,true), - (5,22,'5000',3,10,true), - (6,23,'25d93b3f-c3aa-4814-9d5e-c7c96ced7762',5,10,true); diff --git a/acl/src/test/resources/jdbcMutableAclServiceTests-context.xml b/acl/src/test/resources/jdbcMutableAclServiceTests-context.xml deleted file mode 100644 index d23a727141c..00000000000 --- a/acl/src/test/resources/jdbcMutableAclServiceTests-context.xml +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/acl/src/test/resources/jdbcMutableAclServiceTestsWithAclClass-context.xml b/acl/src/test/resources/jdbcMutableAclServiceTestsWithAclClass-context.xml deleted file mode 100644 index 0e3d0327633..00000000000 --- a/acl/src/test/resources/jdbcMutableAclServiceTestsWithAclClass-context.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/acl/src/test/resources/logback-test.xml b/acl/src/test/resources/logback-test.xml deleted file mode 100644 index 2d51ba4180a..00000000000 --- a/acl/src/test/resources/logback-test.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - diff --git a/docs/antora-playbook.yml b/antora-playbook.yml similarity index 53% rename from docs/antora-playbook.yml rename to antora-playbook.yml index c2ce0b76e7e..af31d6955cd 100644 --- a/docs/antora-playbook.yml +++ b/antora-playbook.yml @@ -1,7 +1,7 @@ antora: extensions: - - require: '@springio/antora-extensions' - root_component_name: 'security' + - require: '@springio/antora-extensions' + root_component_name: 'security' site: title: Spring Security url: https://docs.spring.io/spring-security/reference @@ -10,10 +10,10 @@ git: ensure_git_suffix: false content: sources: - - url: https://github.com/spring-projects/spring-security - branches: [main, '5.{{6..9},{1..9}+({0..9})}.x', '6.+({0..9}).x'] - tags: ['5.{{6..9},{1..9}+({0..9})}.{0..99}?(-RC+({0..9}))', '6.+({0..9}).+({0..9})?(-{RC,M}*)','!(5.6.{0..10}*)', '!(5.7.{0..8}*)', '!(5.8.{0..3}?({-RC,-M}+({0..9})))','!(6.0.{0..3}*)','!(6.1.0*)'] - start_path: docs + - url: https://github.com/spring-projects/spring-security + branches: [main, '6.{4..5}.x', '{7..99}.*.x'] + tags: ['6.{4..5}.*','{7..99}.*'] + start_path: docs asciidoc: attributes: page-stackoverflow-url: https://stackoverflow.com/tags/spring-security @@ -22,16 +22,16 @@ asciidoc: hide-uri-scheme: '@' tabs-sync-option: '@' extensions: - - '@asciidoctor/tabs' - - '@springio/asciidoctor-extensions' - - '@springio/asciidoctor-extensions/javadoc-extension' + - '@asciidoctor/tabs' + - '@springio/asciidoctor-extensions' + - '@springio/asciidoctor-extensions/javadoc-extension' urls: latest_version_segment_strategy: redirect:to latest_version_segment: '' redirect_facility: httpd ui: bundle: - url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.18/ui-bundle.zip + url: https://github.com/spring-io/antora-ui-spring/releases/download/v0.4.23/ui-bundle.zip snapshot: true runtime: log: diff --git a/aspects/spring-security-aspects.gradle b/aspects/spring-security-aspects.gradle deleted file mode 100644 index 9df4e615383..00000000000 --- a/aspects/spring-security-aspects.gradle +++ /dev/null @@ -1,36 +0,0 @@ -apply plugin: 'io.spring.convention.spring-module' -apply plugin: 'io.freefair.aspectj' - -compileAspectj { - sourceCompatibility "17" - targetCompatibility "17" -} -compileTestAspectj { - sourceCompatibility "17" - targetCompatibility "17" -} - -dependencies { - management platform(project(":spring-security-dependencies")) - api "org.aspectj:aspectjrt" - api project(':spring-security-core') - api 'org.springframework:spring-beans' - api 'org.springframework:spring-context' - api 'org.springframework:spring-core' - - optional project(':spring-security-access') - - testImplementation 'org.springframework:spring-aop' - testImplementation "org.assertj:assertj-core" - testImplementation "org.junit.jupiter:junit-jupiter-api" - testImplementation "org.junit.jupiter:junit-jupiter-params" - testImplementation "org.junit.jupiter:junit-jupiter-engine" - testImplementation "org.mockito:mockito-core" - testImplementation "org.mockito:mockito-junit-jupiter" - testImplementation "org.springframework:spring-test" - testAspect sourceSets.main.output - - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' -} - -compileAspectj.ajcOptions.outxmlfile = "META-INF/aop.xml" diff --git a/aspects/src/main/java/org/springframework/security/access/intercept/aspectj/aspect/AnnotationSecurityAspect.aj b/aspects/src/main/java/org/springframework/security/access/intercept/aspectj/aspect/AnnotationSecurityAspect.aj deleted file mode 100644 index ef48ffdfc6d..00000000000 --- a/aspects/src/main/java/org/springframework/security/access/intercept/aspectj/aspect/AnnotationSecurityAspect.aj +++ /dev/null @@ -1,92 +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.access.intercept.aspectj.aspect; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.access.annotation.Secured; -import org.springframework.security.access.intercept.aspectj.AspectJCallback; -import org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor; -import org.springframework.security.access.prepost.PostAuthorize; -import org.springframework.security.access.prepost.PostFilter; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.access.prepost.PreFilter; - -/** - * Concrete AspectJ aspect using Spring Security @Secured annotation - * for JDK 1.5+. - * - *

- * When using this aspect, you must annotate the implementation class - * (and/or methods within that class), not the interface (if any) that - * the class implements. AspectJ follows Java's rule that annotations on - * interfaces are not inherited. This will vary from Spring AOP. - * - * @author Mike Wiesner - * @author Luke Taylor - * @since 3.1 - * @deprecated Use aspects in {@link org.springframework.security.authorization.method.aspectj} instead - */ -@Deprecated -public aspect AnnotationSecurityAspect implements InitializingBean { - - /** - * Matches the execution of any public method in a type with the Secured - * annotation, or any subtype of a type with the Secured annotation. - */ - private pointcut executionOfAnyPublicMethodInAtSecuredType() : - execution(public * ((@Secured *)+).*(..)) && @this(Secured); - - /** - * Matches the execution of any method with the Secured annotation. - */ - private pointcut executionOfSecuredMethod() : - execution(* *(..)) && @annotation(Secured); - - /** - * Matches the execution of any method with Pre/Post annotations. - */ - private pointcut executionOfPrePostAnnotatedMethod() : - execution(* *(..)) && (@annotation(PreAuthorize) || @annotation(PreFilter) - || @annotation(PostAuthorize) || @annotation(PostFilter)); - - private pointcut securedMethodExecution() : - executionOfAnyPublicMethodInAtSecuredType() || - executionOfSecuredMethod() || - executionOfPrePostAnnotatedMethod(); - - private AspectJMethodSecurityInterceptor securityInterceptor; - - Object around(): securedMethodExecution() { - if (this.securityInterceptor == null) { - return proceed(); - } - - AspectJCallback callback = () -> proceed(); - - return this.securityInterceptor.invoke(thisJoinPoint, callback); - } - - public void setSecurityInterceptor(AspectJMethodSecurityInterceptor securityInterceptor) { - this.securityInterceptor = securityInterceptor; - } - - public void afterPropertiesSet() { - if (this.securityInterceptor == null) { - throw new IllegalArgumentException("securityInterceptor required"); - } - } - -} diff --git a/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/AbstractMethodInterceptorAspect.aj b/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/AbstractMethodInterceptorAspect.aj deleted file mode 100644 index 2f14e18e9da..00000000000 --- a/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/AbstractMethodInterceptorAspect.aj +++ /dev/null @@ -1,61 +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.authorization.method.aspectj; - -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.access.prepost.PostAuthorize; - -/** - * Abstract AspectJ aspect for adapting a {@link MethodInvocation} - * - * @author Josh Cummings - * @since 5.8 - */ -abstract aspect AbstractMethodInterceptorAspect { - - protected abstract pointcut executionOfAnnotatedMethod(); - - private MethodInterceptor securityInterceptor; - - Object around(): executionOfAnnotatedMethod() { - if (this.securityInterceptor == null) { - return proceed(); - } - MethodInvocation invocation = new JoinPointMethodInvocation(thisJoinPoint, () -> proceed()); - try { - return this.securityInterceptor.invoke(invocation); - } catch (Throwable t) { - throwUnchecked(t); - throw new IllegalStateException("Code unexpectedly reached", t); - } - } - - public void setSecurityInterceptor(MethodInterceptor securityInterceptor) { - this.securityInterceptor = securityInterceptor; - } - - private static void throwUnchecked(Throwable ex) { - AbstractMethodInterceptorAspect.throwAny(ex); - } - - @SuppressWarnings("unchecked") - private static void throwAny(Throwable ex) throws E { - throw (E) ex; - } -} diff --git a/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/JoinPointMethodInvocation.aj b/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/JoinPointMethodInvocation.aj deleted file mode 100644 index bf07c302107..00000000000 --- a/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/JoinPointMethodInvocation.aj +++ /dev/null @@ -1,98 +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.authorization.method.aspectj; - -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Method; -import java.util.function.Supplier; - -import org.aopalliance.intercept.MethodInvocation; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.reflect.CodeSignature; - -import org.springframework.util.Assert; - -class JoinPointMethodInvocation implements MethodInvocation { - - private final JoinPoint jp; - - private final Method method; - - private final Object target; - - private final Supplier proceed; - - JoinPointMethodInvocation(JoinPoint jp, Supplier proceed) { - this.jp = jp; - if (jp.getTarget() != null) { - this.target = jp.getTarget(); - } - else { - // SEC-1295: target may be null if an ITD is in use - this.target = jp.getSignature().getDeclaringType(); - } - String targetMethodName = jp.getStaticPart().getSignature().getName(); - Class[] types = ((CodeSignature) jp.getStaticPart().getSignature()).getParameterTypes(); - Class declaringType = jp.getStaticPart().getSignature().getDeclaringType(); - this.method = findMethod(targetMethodName, declaringType, types); - Assert.notNull(this.method, () -> "Could not obtain target method from JoinPoint: '" + jp + "'"); - this.proceed = proceed; - } - - private Method findMethod(String name, Class declaringType, Class[] params) { - Method method = null; - try { - method = declaringType.getMethod(name, params); - } - catch (NoSuchMethodException ignored) { - } - if (method == null) { - try { - method = declaringType.getDeclaredMethod(name, params); - } - catch (NoSuchMethodException ignored) { - } - } - return method; - } - - @Override - public Method getMethod() { - return this.method; - } - - @Override - public Object[] getArguments() { - return this.jp.getArgs(); - } - - @Override - public AccessibleObject getStaticPart() { - return this.method; - } - - @Override - public Object getThis() { - return this.target; - } - - @Override - public Object proceed() throws Throwable { - return this.proceed.get(); - } - -} diff --git a/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/PostAuthorizeAspect.aj b/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/PostAuthorizeAspect.aj deleted file mode 100644 index e6578ff9fcd..00000000000 --- a/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/PostAuthorizeAspect.aj +++ /dev/null @@ -1,44 +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.authorization.method.aspectj; - -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.access.prepost.PostAuthorize; - -/** - * Concrete AspectJ aspect using Spring Security @PostAuthorize annotation. - * - *

- * When using this aspect, you must annotate the implementation class - * (and/or methods within that class), not the interface (if any) that - * the class implements. AspectJ follows Java's rule that annotations on - * interfaces are not inherited. This will vary from Spring AOP. - * - * @author Mike Wiesner - * @author Luke Taylor - * @author Josh Cummings - * @since 5.8 - */ -public aspect PostAuthorizeAspect extends AbstractMethodInterceptorAspect { - - /** - * Matches the execution of any method with a PostAuthorize annotation. - */ - protected pointcut executionOfAnnotatedMethod() : execution(* *(..)) && @annotation(PostAuthorize); -} diff --git a/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/PostFilterAspect.aj b/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/PostFilterAspect.aj deleted file mode 100644 index c0331e8795e..00000000000 --- a/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/PostFilterAspect.aj +++ /dev/null @@ -1,45 +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.authorization.method.aspectj; - -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.access.prepost.PostFilter; - -/** - * Concrete AspectJ aspect using Spring Security @PostFilter annotation. - * - *

- * When using this aspect, you must annotate the implementation class - * (and/or methods within that class), not the interface (if any) that - * the class implements. AspectJ follows Java's rule that annotations on - * interfaces are not inherited. This will vary from Spring AOP. - * - * @author Mike Wiesner - * @author Luke Taylor - * @author Josh Cummings - * @since 5.8 - */ -public aspect PostFilterAspect extends AbstractMethodInterceptorAspect { - - /** - * Matches the execution of any method with a PostFilter annotation. - */ - protected pointcut executionOfAnnotatedMethod() : execution(* *(..)) && @annotation(PostFilter); - -} diff --git a/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/PreAuthorizeAspect.aj b/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/PreAuthorizeAspect.aj deleted file mode 100644 index f7e16c16f78..00000000000 --- a/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/PreAuthorizeAspect.aj +++ /dev/null @@ -1,44 +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.authorization.method.aspectj; - -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.access.prepost.PreAuthorize; - -/** - * Concrete AspectJ aspect using Spring Security @PreAuthorize annotation. - * - *

- * When using this aspect, you must annotate the implementation class - * (and/or methods within that class), not the interface (if any) that - * the class implements. AspectJ follows Java's rule that annotations on - * interfaces are not inherited. This will vary from Spring AOP. - * - * @author Mike Wiesner - * @author Luke Taylor - * @author Josh Cummings - * @since 5.8 - */ -public aspect PreAuthorizeAspect extends AbstractMethodInterceptorAspect { - - /** - * Matches the execution of any method with a PreAuthorize annotation. - */ - protected pointcut executionOfAnnotatedMethod() : execution(* *(..)) && @annotation(PreAuthorize); -} diff --git a/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/PreFilterAspect.aj b/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/PreFilterAspect.aj deleted file mode 100644 index 618ddea224b..00000000000 --- a/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/PreFilterAspect.aj +++ /dev/null @@ -1,45 +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.authorization.method.aspectj; - -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.access.prepost.PreFilter; - -/** - * Concrete AspectJ aspect using Spring Security @PreFilter annotation. - * - *

- * When using this aspect, you must annotate the implementation class - * (and/or methods within that class), not the interface (if any) that - * the class implements. AspectJ follows Java's rule that annotations on - * interfaces are not inherited. This will vary from Spring AOP. - * - * @author Mike Wiesner - * @author Luke Taylor - * @author Josh Cummings - * @since 5.8 - */ -public aspect PreFilterAspect extends AbstractMethodInterceptorAspect { - - /** - * Matches the execution of any method with a PreFilter annotation. - */ - protected pointcut executionOfAnnotatedMethod() : execution(* *(..)) && @annotation(PreFilter); - -} diff --git a/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/SecuredAspect.aj b/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/SecuredAspect.aj deleted file mode 100644 index ab3f3a553c5..00000000000 --- a/aspects/src/main/java/org/springframework/security/authorization/method/aspectj/SecuredAspect.aj +++ /dev/null @@ -1,56 +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.authorization.method.aspectj; - -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.security.access.annotation.Secured; - -/** - * Concrete AspectJ aspect using Spring Security @Secured annotation. - * - *

- * When using this aspect, you must annotate the implementation class - * (and/or methods within that class), not the interface (if any) that - * the class implements. AspectJ follows Java's rule that annotations on - * interfaces are not inherited. This will vary from Spring AOP. - * - * @author Mike Wiesner - * @author Luke Taylor - * @author Josh Cummings - * @since 5.8 - */ -public aspect SecuredAspect extends AbstractMethodInterceptorAspect { - - /** - * Matches the execution of any public method in a type with the Secured - * annotation, or any subtype of a type with the Secured annotation. - */ - private pointcut executionOfAnyPublicMethodInAtSecuredType() : - execution(public * ((@Secured *)+).*(..)) && @this(Secured); - - /** - * Matches the execution of any method with the Secured annotation. - */ - private pointcut executionOfSecuredMethod() : - execution(* *(..)) && @annotation(Secured); - - protected pointcut executionOfAnnotatedMethod() : - executionOfAnyPublicMethodInAtSecuredType() || - executionOfSecuredMethod(); -} diff --git a/aspects/src/test/java/org/springframework/security/access/intercept/aspectj/aspect/AnnotationSecurityAspectTests.java b/aspects/src/test/java/org/springframework/security/access/intercept/aspectj/aspect/AnnotationSecurityAspectTests.java deleted file mode 100644 index 16c11bc83c7..00000000000 --- a/aspects/src/test/java/org/springframework/security/access/intercept/aspectj/aspect/AnnotationSecurityAspectTests.java +++ /dev/null @@ -1,221 +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.access.intercept.aspectj.aspect; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import org.springframework.security.access.AccessDecisionVoter; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.annotation.Secured; -import org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource; -import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; -import org.springframework.security.access.expression.method.ExpressionBasedAnnotationAttributeFactory; -import org.springframework.security.access.expression.method.ExpressionBasedPostInvocationAdvice; -import org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice; -import org.springframework.security.access.intercept.AfterInvocationProviderManager; -import org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor; -import org.springframework.security.access.prepost.PostFilter; -import org.springframework.security.access.prepost.PostInvocationAdviceProvider; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter; -import org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource; -import org.springframework.security.access.vote.AffirmativeBased; -import org.springframework.security.access.vote.RoleVoter; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Luke Taylor - * @since 3.0.3 - */ -public class AnnotationSecurityAspectTests { - - private AffirmativeBased adm; - - @Mock - private AuthenticationManager authman; - - private TestingAuthenticationToken anne = new TestingAuthenticationToken("anne", "", "ROLE_A"); - - // private TestingAuthenticationToken bob = new TestingAuthenticationToken("bob", "", - // "ROLE_B"); - private AspectJMethodSecurityInterceptor interceptor; - - private SecuredImpl secured = new SecuredImpl(); - - private SecuredImplSubclass securedSub = new SecuredImplSubclass(); - - private PrePostSecured prePostSecured = new PrePostSecured(); - - @BeforeEach - public final void setUp() { - MockitoAnnotations.initMocks(this); - this.interceptor = new AspectJMethodSecurityInterceptor(); - AccessDecisionVoter[] voters = new AccessDecisionVoter[] { new RoleVoter(), - new PreInvocationAuthorizationAdviceVoter(new ExpressionBasedPreInvocationAdvice()) }; - this.adm = new AffirmativeBased(Arrays.>asList(voters)); - this.interceptor.setAccessDecisionManager(this.adm); - this.interceptor.setAuthenticationManager(this.authman); - this.interceptor.setSecurityMetadataSource(new SecuredAnnotationSecurityMetadataSource()); - AnnotationSecurityAspect secAspect = AnnotationSecurityAspect.aspectOf(); - secAspect.setSecurityInterceptor(this.interceptor); - } - - @AfterEach - public void clearContext() { - SecurityContextHolder.clearContext(); - } - - @Test - public void securedInterfaceMethodAllowsAllAccess() { - this.secured.securedMethod(); - } - - @Test - public void securedClassMethodDeniesUnauthenticatedAccess() { - assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) - .isThrownBy(() -> this.secured.securedClassMethod()); - } - - @Test - public void securedClassMethodAllowsAccessToRoleA() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - this.secured.securedClassMethod(); - } - - @Test - public void internalPrivateCallIsIntercepted() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.publicCallsPrivate()); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.securedSub.publicCallsPrivate()); - } - - @Test - public void protectedMethodIsIntercepted() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.protectedMethod()); - } - - @Test - public void overriddenProtectedMethodIsNotIntercepted() { - // AspectJ doesn't inherit annotations - this.securedSub.protectedMethod(); - } - - // SEC-1262 - @Test - public void denyAllPreAuthorizeDeniesAccess() { - configureForElAnnotations(); - SecurityContextHolder.getContext().setAuthentication(this.anne); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.prePostSecured::denyAllMethod); - } - - @Test - public void postFilterIsApplied() { - configureForElAnnotations(); - SecurityContextHolder.getContext().setAuthentication(this.anne); - List objects = this.prePostSecured.postFilterMethod(); - assertThat(objects).hasSize(2); - assertThat(objects).contains("apple"); - assertThat(objects).contains("aubergine"); - } - - private void configureForElAnnotations() { - DefaultMethodSecurityExpressionHandler eh = new DefaultMethodSecurityExpressionHandler(); - this.interceptor.setSecurityMetadataSource( - new PrePostAnnotationSecurityMetadataSource(new ExpressionBasedAnnotationAttributeFactory(eh))); - this.interceptor.setAccessDecisionManager(this.adm); - AfterInvocationProviderManager aim = new AfterInvocationProviderManager(); - aim.setProviders(Arrays.asList(new PostInvocationAdviceProvider(new ExpressionBasedPostInvocationAdvice(eh)))); - this.interceptor.setAfterInvocationManager(aim); - } - - interface SecuredInterface { - - @Secured("ROLE_X") - void securedMethod(); - - } - - static class SecuredImpl implements SecuredInterface { - - // Not really secured because AspectJ doesn't inherit annotations from interfaces - @Override - public void securedMethod() { - } - - @Secured("ROLE_A") - public void securedClassMethod() { - } - - @Secured("ROLE_X") - private void privateMethod() { - } - - @Secured("ROLE_X") - protected void protectedMethod() { - } - - @Secured("ROLE_X") - public void publicCallsPrivate() { - privateMethod(); - } - - } - - static class SecuredImplSubclass extends SecuredImpl { - - @Override - protected void protectedMethod() { - } - - @Override - public void publicCallsPrivate() { - super.publicCallsPrivate(); - } - - } - - static class PrePostSecured { - - @PreAuthorize("denyAll") - public void denyAllMethod() { - } - - @PostFilter("filterObject.startsWith('a')") - public List postFilterMethod() { - ArrayList objects = new ArrayList<>(); - objects.addAll(Arrays.asList(new String[] { "apple", "banana", "aubergine", "orange" })); - return objects; - } - - } - -} diff --git a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PostAuthorizeAspectTests.java b/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PostAuthorizeAspectTests.java deleted file mode 100644 index 954abb3bd19..00000000000 --- a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PostAuthorizeAspectTests.java +++ /dev/null @@ -1,180 +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.authorization.method.aspectj; - -import org.aopalliance.intercept.MethodInterceptor; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.MockitoAnnotations; - -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.prepost.PostAuthorize; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor; -import org.springframework.security.core.context.SecurityContextHolder; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Luke Taylor - * @since 3.0.3 - */ -public class PostAuthorizeAspectTests { - - private TestingAuthenticationToken anne = new TestingAuthenticationToken("anne", "", "ROLE_A"); - - private MethodInterceptor interceptor; - - private SecuredImpl secured = new SecuredImpl(); - - private SecuredImplSubclass securedSub = new SecuredImplSubclass(); - - private PrePostSecured prePostSecured = new PrePostSecured(); - - @BeforeEach - public final void setUp() { - MockitoAnnotations.initMocks(this); - this.interceptor = AuthorizationManagerAfterMethodInterceptor.postAuthorize(); - PostAuthorizeAspect secAspect = PostAuthorizeAspect.aspectOf(); - secAspect.setSecurityInterceptor(this.interceptor); - } - - @AfterEach - public void clearContext() { - SecurityContextHolder.clearContext(); - } - - @Test - public void securedInterfaceMethodAllowsAllAccess() { - this.secured.securedMethod(); - } - - @Test - public void securedClassMethodDeniesUnauthenticatedAccess() { - assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) - .isThrownBy(() -> this.secured.securedClassMethod()); - } - - @Test - public void securedClassMethodAllowsAccessToRoleA() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - this.secured.securedClassMethod(); - } - - @Test - public void internalPrivateCallIsIntercepted() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.publicCallsPrivate()); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.securedSub.publicCallsPrivate()); - } - - @Test - public void protectedMethodIsIntercepted() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.protectedMethod()); - } - - @Test - public void overriddenProtectedMethodIsNotIntercepted() { - // AspectJ doesn't inherit annotations - this.securedSub.protectedMethod(); - } - - // SEC-1262 - @Test - public void denyAllPreAuthorizeDeniesAccess() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.prePostSecured::denyAllMethod); - } - - @Test - public void nestedDenyAllPostAuthorizeDeniesAccess() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.secured.myObject().denyAllMethod()); - } - - interface SecuredInterface { - - @PostAuthorize("hasRole('X')") - void securedMethod(); - - } - - static class SecuredImpl implements SecuredInterface { - - // Not really secured because AspectJ doesn't inherit annotations from interfaces - @Override - public void securedMethod() { - } - - @PostAuthorize("hasRole('A')") - void securedClassMethod() { - } - - @PostAuthorize("hasRole('X')") - private void privateMethod() { - } - - @PostAuthorize("hasRole('X')") - protected void protectedMethod() { - } - - @PostAuthorize("hasRole('X')") - void publicCallsPrivate() { - privateMethod(); - } - - NestedObject myObject() { - return new NestedObject(); - } - - } - - static class SecuredImplSubclass extends SecuredImpl { - - @Override - protected void protectedMethod() { - } - - @Override - public void publicCallsPrivate() { - super.publicCallsPrivate(); - } - - } - - static class PrePostSecured { - - @PostAuthorize("denyAll") - void denyAllMethod() { - } - - } - - static class NestedObject { - - @PostAuthorize("denyAll") - void denyAllMethod() { - - } - - } - -} diff --git a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PostFilterAspectTests.java b/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PostFilterAspectTests.java deleted file mode 100644 index 22caf20fe98..00000000000 --- a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PostFilterAspectTests.java +++ /dev/null @@ -1,84 +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.authorization.method.aspectj; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.aopalliance.intercept.MethodInterceptor; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.MockitoAnnotations; - -import org.springframework.security.access.prepost.PostFilter; -import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Luke Taylor - * @since 3.0.3 - */ -public class PostFilterAspectTests { - - private MethodInterceptor interceptor; - - private PrePostSecured prePostSecured = new PrePostSecured(); - - @BeforeEach - public final void setUp() { - MockitoAnnotations.initMocks(this); - this.interceptor = new PostFilterAuthorizationMethodInterceptor(); - PostFilterAspect secAspect = PostFilterAspect.aspectOf(); - secAspect.setSecurityInterceptor(this.interceptor); - } - - @Test - public void preFilterMethodWhenListThenFilters() { - List objects = new ArrayList<>(Arrays.asList("apple", "banana", "aubergine", "orange")); - assertThat(this.prePostSecured.postFilterMethod(objects)).containsExactly("apple", "aubergine"); - } - - @Test - public void nestedDenyAllPostFilterDeniesAccess() { - assertThat(this.prePostSecured.myObject().denyAllMethod()).isEmpty(); - } - - static class PrePostSecured { - - @PostFilter("filterObject.startsWith('a')") - List postFilterMethod(List objects) { - return objects; - } - - NestedObject myObject() { - return new NestedObject(); - } - - } - - static class NestedObject { - - @PostFilter("filterObject == null") - List denyAllMethod() { - return new ArrayList<>(List.of("deny")); - } - - } - -} diff --git a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreAuthorizeAspectTests.java b/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreAuthorizeAspectTests.java deleted file mode 100644 index 70d22982b2e..00000000000 --- a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreAuthorizeAspectTests.java +++ /dev/null @@ -1,203 +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.authorization.method.aspectj; - -import org.aopalliance.intercept.MethodInterceptor; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.MockitoAnnotations; - -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; -import org.springframework.security.core.context.SecurityContextHolder; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Luke Taylor - * @since 3.0.3 - */ -public class PreAuthorizeAspectTests { - - private TestingAuthenticationToken anne = new TestingAuthenticationToken("anne", "", "ROLE_A"); - - private MethodInterceptor interceptor; - - private SecuredImpl secured = new SecuredImpl(); - - private SecuredImplSubclass securedSub = new SecuredImplSubclass(); - - private PrePostSecured prePostSecured = new PrePostSecured(); - - private MultipleInterfaces multiple = new MultipleInterfaces(); - - @BeforeEach - public final void setUp() { - MockitoAnnotations.initMocks(this); - this.interceptor = AuthorizationManagerBeforeMethodInterceptor.preAuthorize(); - PreAuthorizeAspect secAspect = PreAuthorizeAspect.aspectOf(); - secAspect.setSecurityInterceptor(this.interceptor); - } - - @AfterEach - public void clearContext() { - SecurityContextHolder.clearContext(); - } - - @Test - public void securedInterfaceMethodAllowsAllAccess() { - this.secured.securedMethod(); - } - - @Test - public void securedClassMethodDeniesUnauthenticatedAccess() { - assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) - .isThrownBy(() -> this.secured.securedClassMethod()); - } - - @Test - public void securedClassMethodAllowsAccessToRoleA() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - this.secured.securedClassMethod(); - } - - @Test - public void internalPrivateCallIsIntercepted() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.publicCallsPrivate()); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.securedSub.publicCallsPrivate()); - } - - @Test - public void protectedMethodIsIntercepted() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.protectedMethod()); - } - - @Test - public void overriddenProtectedMethodIsNotIntercepted() { - // AspectJ doesn't inherit annotations - this.securedSub.protectedMethod(); - } - - // SEC-1262 - @Test - public void denyAllPreAuthorizeDeniesAccess() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.prePostSecured::denyAllMethod); - } - - @Test - public void nestedDenyAllPreAuthorizeDeniesAccess() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.secured.myObject().denyAllMethod()); - } - - @Test - public void multipleInterfacesPreAuthorizeAllows() { - // aspectj doesn't inherit annotations - this.multiple.securedMethod(); - } - - interface SecuredInterface { - - @PreAuthorize("hasRole('X')") - void securedMethod(); - - } - - static class SecuredImpl implements SecuredInterface { - - // Not really secured because AspectJ doesn't inherit annotations from interfaces - @Override - public void securedMethod() { - } - - @PreAuthorize("hasRole('A')") - void securedClassMethod() { - } - - @PreAuthorize("hasRole('X')") - private void privateMethod() { - } - - @PreAuthorize("hasRole('X')") - protected void protectedMethod() { - } - - @PreAuthorize("hasRole('A')") - void publicCallsPrivate() { - privateMethod(); - } - - NestedObject myObject() { - return new NestedObject(); - } - - } - - static class SecuredImplSubclass extends SecuredImpl { - - @Override - protected void protectedMethod() { - } - - @Override - public void publicCallsPrivate() { - super.publicCallsPrivate(); - } - - } - - static class PrePostSecured { - - @PreAuthorize("denyAll") - void denyAllMethod() { - } - - } - - static class NestedObject { - - @PreAuthorize("denyAll") - void denyAllMethod() { - - } - - } - - interface AnotherSecuredInterface { - - @PreAuthorize("hasRole('Y')") - void securedMethod(); - - } - - static class MultipleInterfaces implements SecuredInterface, AnotherSecuredInterface { - - @Override - public void securedMethod() { - } - - } - -} diff --git a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreFilterAspectTests.java b/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreFilterAspectTests.java deleted file mode 100644 index 3d970bb5a5b..00000000000 --- a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/PreFilterAspectTests.java +++ /dev/null @@ -1,84 +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.authorization.method.aspectj; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import org.aopalliance.intercept.MethodInterceptor; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.MockitoAnnotations; - -import org.springframework.security.access.prepost.PreFilter; -import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Luke Taylor - * @since 3.0.3 - */ -public class PreFilterAspectTests { - - private MethodInterceptor interceptor; - - private PrePostSecured prePostSecured = new PrePostSecured(); - - @BeforeEach - public final void setUp() { - MockitoAnnotations.initMocks(this); - this.interceptor = new PreFilterAuthorizationMethodInterceptor(); - PreFilterAspect secAspect = PreFilterAspect.aspectOf(); - secAspect.setSecurityInterceptor(this.interceptor); - } - - @Test - public void preFilterMethodWhenListThenFilters() { - List objects = new ArrayList<>(Arrays.asList("apple", "banana", "aubergine", "orange")); - assertThat(this.prePostSecured.preFilterMethod(objects)).containsExactly("apple", "aubergine"); - } - - @Test - public void nestedDenyAllPreFilterDeniesAccess() { - assertThat(this.prePostSecured.myObject().denyAllMethod(new ArrayList<>(List.of("deny")))).isEmpty(); - } - - static class PrePostSecured { - - @PreFilter("filterObject.startsWith('a')") - List preFilterMethod(List objects) { - return objects; - } - - NestedObject myObject() { - return new NestedObject(); - } - - } - - static class NestedObject { - - @PreFilter("filterObject == null") - List denyAllMethod(List list) { - return list; - } - - } - -} diff --git a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/SecuredAspectTests.java b/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/SecuredAspectTests.java deleted file mode 100644 index 54907f345d1..00000000000 --- a/aspects/src/test/java/org/springframework/security/authorization/method/aspectj/SecuredAspectTests.java +++ /dev/null @@ -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.authorization.method.aspectj; - -import org.aopalliance.intercept.MethodInterceptor; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.MockitoAnnotations; - -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.annotation.Secured; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor; -import org.springframework.security.core.context.SecurityContextHolder; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Luke Taylor - * @since 3.0.3 - */ -public class SecuredAspectTests { - - private TestingAuthenticationToken anne = new TestingAuthenticationToken("anne", "", "ROLE_A"); - - private MethodInterceptor interceptor; - - private SecuredImpl secured = new SecuredImpl(); - - private SecuredImplSubclass securedSub = new SecuredImplSubclass(); - - @BeforeEach - public final void setUp() { - MockitoAnnotations.initMocks(this); - this.interceptor = AuthorizationManagerBeforeMethodInterceptor.secured(); - SecuredAspect secAspect = SecuredAspect.aspectOf(); - secAspect.setSecurityInterceptor(this.interceptor); - } - - @AfterEach - public void clearContext() { - SecurityContextHolder.clearContext(); - } - - @Test - public void securedInterfaceMethodAllowsAllAccess() { - this.secured.securedMethod(); - } - - @Test - public void securedClassMethodDeniesUnauthenticatedAccess() { - assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) - .isThrownBy(() -> this.secured.securedClassMethod()); - } - - @Test - public void securedClassMethodAllowsAccessToRoleA() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - this.secured.securedClassMethod(); - } - - @Test - public void internalPrivateCallIsIntercepted() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.publicCallsPrivate()); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.securedSub.publicCallsPrivate()); - } - - @Test - public void protectedMethodIsIntercepted() { - SecurityContextHolder.getContext().setAuthentication(this.anne); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.secured.protectedMethod()); - } - - @Test - public void overriddenProtectedMethodIsNotIntercepted() { - // AspectJ doesn't inherit annotations - this.securedSub.protectedMethod(); - } - - interface SecuredInterface { - - @Secured("ROLE_X") - void securedMethod(); - - } - - static class SecuredImpl implements SecuredInterface { - - // Not really secured because AspectJ doesn't inherit annotations from interfaces - @Override - public void securedMethod() { - } - - @Secured("ROLE_A") - void securedClassMethod() { - } - - @Secured("ROLE_X") - private void privateMethod() { - } - - @Secured("ROLE_X") - protected void protectedMethod() { - } - - @Secured("ROLE_X") - void publicCallsPrivate() { - privateMethod(); - } - - } - - static class SecuredImplSubclass extends SecuredImpl { - - @Override - protected void protectedMethod() { - } - - @Override - public void publicCallsPrivate() { - super.publicCallsPrivate(); - } - - } - -} diff --git a/aspects/src/test/resources/logback-test.xml b/aspects/src/test/resources/logback-test.xml deleted file mode 100644 index 2d51ba4180a..00000000000 --- a/aspects/src/test/resources/logback-test.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n - - - - - - - - - - - diff --git a/bom/spring-security-bom.gradle b/bom/spring-security-bom.gradle deleted file mode 100644 index 69b6d399a2a..00000000000 --- a/bom/spring-security-bom.gradle +++ /dev/null @@ -1,13 +0,0 @@ -import io.spring.gradle.convention.SpringModulePlugin - -apply plugin: 'io.spring.convention.bom' - -dependencies { - constraints { - project.rootProject.allprojects { p -> - p.plugins.withType(SpringModulePlugin) { - api p - } - } - } -} diff --git a/build.gradle b/build.gradle index 1f64875227e..e7d2bc510d4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,130 +1,8 @@ -import io.spring.gradle.IncludeRepoTask -import trang.RncToXsd - -buildscript { - dependencies { - classpath libs.io.spring.javaformat.spring.javaformat.gradle.plugin - classpath libs.io.spring.nohttp.nohttp.gradle - classpath libs.io.freefair.gradle.aspectj.plugin - classpath libs.org.jetbrains.kotlin.kotlin.gradle.plugin - classpath libs.com.netflix.nebula.nebula.project.plugin - } - repositories { - maven { url 'https://plugins.gradle.org/m2/' } - } -} - plugins { - alias(libs.plugins.org.gradle.wrapper.upgrade) -} - -apply plugin: 'io.spring.nohttp' -apply plugin: 'locks' -apply plugin: 'io.spring.convention.root' -apply plugin: 'org.jetbrains.kotlin.jvm' -apply plugin: 'org.springframework.security.versions.verify-dependencies-versions' -apply plugin: 'org.springframework.security.check-expected-branch-version' -apply plugin: 'io.spring.security.release' - -group = 'org.springframework.security' -description = 'Spring Security' - -ext.snapshotBuild = version.contains("SNAPSHOT") -ext.releaseBuild = version.contains("SNAPSHOT") -ext.milestoneBuild = !(snapshotBuild || releaseBuild) - -repositories { - mavenCentral() - maven { url "https://repo.spring.io/milestone" } -} - -springRelease { - weekOfMonth = 3 - dayOfWeek = 1 - referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html" - apiDocUrl = "https://docs.spring.io/spring-security/reference/{version}/api/java/index.html" - 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 { - if (!['spring-security-bom', 'spring-security-docs'].contains(project.name)) { - apply plugin: 'io.spring.javaformat' - apply plugin: 'checkstyle' - - pluginManager.withPlugin("io.spring.convention.checkstyle", { plugin -> - configure(plugin) { - dependencies { - checkstyle libs.io.spring.javaformat.spring.javaformat.checkstyle - } - checkstyle { - toolVersion = '8.34' - } - } - }) - - if (project.name.contains('sample')) { - tasks.whenTaskAdded { task -> - if (task.name.contains('format') || task.name.contains('checkFormat') || task.name.contains("checkstyle")) { - task.enabled = false - } - } - } - } -} - -develocity { - buildScan { - termsOfUseUrl = 'https://gradle.com/help/legal-terms-of-use' - termsOfUseAgree = 'yes' - } -} - -nohttp { - source.exclude "buildSrc/build/**", "javascript/.gradle/**", "javascript/package-lock.json", "javascript/node_modules/**", "javascript/build/**", "javascript/dist/**" - source.builtBy(project(':spring-security-config').tasks.withType(RncToXsd)) -} - -tasks.named('checkstyleNohttp') { - maxHeapSize = '1g' -} - -tasks.register('cloneRepository', IncludeRepoTask) { - repository = project.getProperties().get("repositoryName") - ref = project.getProperties().get("ref") - var defaultDirectory = project.file("build/tmp/clone") - outputDirectory = project.hasProperty("cloneOutputDirectory") ? project.file("$cloneOutputDirectory") : defaultDirectory + id 'base' + id 'org.antora' version '1.0.0' } -wrapperUpgrade { - gradle { - 'spring-security' { - repo = 'spring-projects/spring-security' - baseBranch = '6.3.x' // runs only on 6.3.x and the update is merged forward to main - } - } +antora { + options = [clean: true, fetch: true, stacktrace: true] } diff --git a/buildSrc/.idea/compiler.xml b/buildSrc/.idea/compiler.xml deleted file mode 100644 index 61a9130cd96..00000000000 --- a/buildSrc/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/buildSrc/.idea/gradle.xml b/buildSrc/.idea/gradle.xml deleted file mode 100644 index 5c59556b93d..00000000000 --- a/buildSrc/.idea/gradle.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/buildSrc/.idea/jarRepositories.xml b/buildSrc/.idea/jarRepositories.xml deleted file mode 100644 index 36cc8fcb9de..00000000000 --- a/buildSrc/.idea/jarRepositories.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/buildSrc/.idea/misc.xml b/buildSrc/.idea/misc.xml deleted file mode 100644 index 3a9d81e2a8f..00000000000 --- a/buildSrc/.idea/misc.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/buildSrc/.idea/uiDesigner.xml b/buildSrc/.idea/uiDesigner.xml deleted file mode 100644 index e96534fb27b..00000000000 --- a/buildSrc/.idea/uiDesigner.xml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/buildSrc/.idea/workspace.xml b/buildSrc/.idea/workspace.xml deleted file mode 100644 index 2ca122bc89f..00000000000 --- a/buildSrc/.idea/workspace.xml +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

-

- - -

- - - - - - - - - - """.formatted(token.getToken())); - }); - // @formatter:on - } - - @Test - public void loginWhenNoCredentialsThenRedirectedToLoginPageWithError() throws Exception { - this.spring.register(DefaultLoginPageConfig.class).autowire(); - this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/login?error")); - } - - @Test - public void loginPageWhenErrorThenDefaultLoginPageWithError() throws Exception { - this.spring.register(DefaultLoginPageConfig.class).autowire(); - CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "BaseSpringSpec_CSRFTOKEN"); - String csrfAttributeName = HttpSessionCsrfTokenRepository.class.getName().concat(".CSRF_TOKEN"); - MvcResult mvcResult = this.mvc.perform(post("/login").with(csrf())).andReturn(); - // @formatter:off - this.mvc.perform(get("/login?error").session((MockHttpSession) mvcResult.getRequest().getSession()) - .sessionAttr(csrfAttributeName, csrfToken)) - .andExpect((result) -> { - String defaultErrorMessage = "Invalid credentials"; - CsrfToken token = (CsrfToken) result.getRequest().getAttribute(CsrfToken.class.getName()); - assertThat(result.getResponse().getContentAsString()).isEqualTo(""" - - - - - - - - Please sign in - - - -
- - - - -
- - """.formatted(defaultErrorMessage, token.getToken())); - }); - // @formatter:on - } - - @Test - public void loginWhenValidCredentialsThenRedirectsToDefaultSuccessPage() throws Exception { - this.spring.register(DefaultLoginPageConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password"); - // @formatter:on - this.mvc.perform(loginRequest).andExpect(redirectedUrl("/")); - } - - @Test - public void loginPageWhenLoggedOutThenDefaultLoginPageWithLogoutMessage() throws Exception { - this.spring.register(DefaultLoginPageConfig.class).autowire(); - CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "BaseSpringSpec_CSRFTOKEN"); - String csrfAttributeName = HttpSessionCsrfTokenRepository.class.getName().concat(".CSRF_TOKEN"); - // @formatter:off - this.mvc.perform(get("/login?logout").sessionAttr(csrfAttributeName, csrfToken)) - .andExpect((result) -> { - CsrfToken token = (CsrfToken) result.getRequest().getAttribute(CsrfToken.class.getName()); - assertThat(result.getResponse().getContentAsString()).isEqualTo(""" - - - - - - - - Please sign in - - - -
- - - - -
- - """.formatted(token.getToken())); - }); - } - - @Test - public void cssWhenFormLoginConfiguredThenServesCss() throws Exception { - this.spring.register(DefaultLoginPageConfig.class).autowire(); - this.mvc.perform(get("/default-ui.css")) - .andExpect(status().isOk()) - .andExpect(header().string("content-type", "text/css;charset=UTF-8")) - .andExpect(content().string(containsString("body {"))); - } - - @Test - public void loginPageWhenLoggedOutAndCustomLogoutSuccessHandlerThenDoesNotRenderLoginPage() throws Exception { - this.spring.register(DefaultLoginPageCustomLogoutSuccessHandlerConfig.class).autowire(); - this.mvc.perform(get("/login?logout")).andExpect(content().string("")); - } - - @Test - public void loginPageWhenLoggedOutAndCustomLogoutSuccessUrlThenDoesNotRenderLoginPage() throws Exception { - this.spring.register(DefaultLoginPageCustomLogoutSuccessUrlConfig.class).autowire(); - this.mvc.perform(get("/login?logout")).andExpect(content().string("")); - } - - @Test - public void loginPageWhenRememberConfigureThenDefaultLoginPageWithRememberMeCheckbox() throws Exception { - this.spring.register(DefaultLoginPageWithRememberMeConfig.class).autowire(); - CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "BaseSpringSpec_CSRFTOKEN"); - String csrfAttributeName = HttpSessionCsrfTokenRepository.class.getName().concat(".CSRF_TOKEN"); - // @formatter:off - this.mvc.perform(get("/login").sessionAttr(csrfAttributeName, csrfToken)) - .andExpect((result) -> { - CsrfToken token = (CsrfToken) result.getRequest().getAttribute(CsrfToken.class.getName()); - assertThat(result.getResponse().getContentAsString()).isEqualTo(""" - - - - - - - - Please sign in - - - -
- - - - -
- - """.formatted(token.getToken())); - }); - // @formatter:on - } - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnDefaultLoginPageGeneratingFilter() { - ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(DefaultLoginPageGeneratingFilter.class)); - } - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnUsernamePasswordAuthenticationFilter() { - ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor) - .postProcess(any(UsernamePasswordAuthenticationFilter.class)); - } - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnLoginUrlAuthenticationEntryPoint() { - ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(LoginUrlAuthenticationEntryPoint.class)); - } - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnExceptionTranslationFilter() { - ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(ExceptionTranslationFilter.class)); - } - - @Test - public void configureWhenAuthenticationEntryPointThenNoDefaultLoginPageGeneratingFilter() { - this.spring.register(DefaultLoginWithCustomAuthenticationEntryPointConfig.class).autowire(); - FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class); - assertThat(filterChain.getFilterChains() - .get(0) - .getFilters() - .stream() - .filter((filter) -> filter.getClass().isAssignableFrom(DefaultLoginPageGeneratingFilter.class)) - .count()).isZero(); - } - - @Test - public void configureWhenAuthenticationEntryPointThenDoesNotServeCss() throws Exception { - this.spring.register(DefaultLoginWithCustomAuthenticationEntryPointConfig.class).autowire(); - FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class); - assertThat(filterChain.getFilterChains() - .get(0) - .getFilters() - .stream() - .filter((filter) -> filter.getClass().isAssignableFrom(DefaultResourcesFilter.class)) - .count()).isZero(); - //@formatter:off - this.mvc.perform(get("/default-ui.css")) - .andExpect(status().is3xxRedirection()); - //@formatter:on - } - - @Test - public void formLoginWhenLogoutEnabledThenCreatesDefaultLogoutPage() throws Exception { - this.spring.register(DefaultLogoutPageConfig.class).autowire(); - this.mvc.perform(get("/logout").with(user("user"))).andExpect(status().isOk()); - } - - @Test - public void formLoginWhenLogoutDisabledThenDefaultLogoutPageDoesNotExist() throws Exception { - this.spring.register(LogoutDisabledConfig.class).autowire(); - this.mvc.perform(get("/logout").with(user("user"))).andExpect(status().isNotFound()); - } - - @Configuration - @EnableWebSecurity - static class DefaultLoginPageConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultLoginPageCustomLogoutSuccessHandlerConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .logout((logout) -> logout - .logoutSuccessHandler(new SimpleUrlLogoutSuccessHandler())) - .formLogin(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultLoginPageCustomLogoutSuccessUrlConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .logout((logout) -> logout - .logoutSuccessUrl("/login?logout")) - .formLogin(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultLoginPageWithRememberMeConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin(withDefaults()) - .rememberMe(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultLoginWithCustomAuthenticationEntryPointConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .exceptionHandling((handling) -> handling - .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))) - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ObjectPostProcessorConfig { - - static ObjectPostProcessor objectPostProcessor; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .exceptionHandling(withDefaults()) - .formLogin(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - static ObjectPostProcessor objectPostProcessor() { - return objectPostProcessor; - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultLogoutPageConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .formLogin(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class LogoutDisabledConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .formLogin(withDefaults()) - .logout((logout) -> logout - .disable() - ); - return http.build(); - // @formatter:on - } - - } - - static class ReflectingObjectPostProcessor implements ObjectPostProcessor { - - @Override - public O postProcess(O object) { - return object; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerAccessDeniedHandlerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerAccessDeniedHandlerTests.java deleted file mode 100644 index ba968dbb386..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerAccessDeniedHandlerTests.java +++ /dev/null @@ -1,158 +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.annotation.web.configurers; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpStatus; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.security.web.access.AccessDeniedHandlerImpl; -import org.springframework.security.web.util.matcher.AnyRequestMatcher; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; - -import static org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher.pathPattern; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Josh Cummings - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class ExceptionHandlingConfigurerAccessDeniedHandlerTests { - - @Autowired - MockMvc mvc; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - @WithMockUser(roles = "ANYTHING") - public void getWhenAccessDeniedOverriddenThenCustomizesResponseByRequest() throws Exception { - this.spring.register(RequestMatcherBasedAccessDeniedHandlerConfig.class).autowire(); - this.mvc.perform(get("/hello")).andExpect(status().isIAmATeapot()); - this.mvc.perform(get("/goodbye")).andExpect(status().isForbidden()); - } - - @Test - @WithMockUser(roles = "ANYTHING") - public void getWhenAccessDeniedOverriddenInLambdaThenCustomizesResponseByRequest() throws Exception { - this.spring.register(RequestMatcherBasedAccessDeniedHandlerInLambdaConfig.class).autowire(); - this.mvc.perform(get("/hello")).andExpect(status().isIAmATeapot()); - this.mvc.perform(get("/goodbye")).andExpect(status().isForbidden()); - } - - @Test - @WithMockUser(roles = "ANYTHING") - public void getWhenAccessDeniedOverriddenByOnlyOneHandlerThenAllRequestsUseThatHandler() throws Exception { - this.spring.register(SingleRequestMatcherAccessDeniedHandlerConfig.class).autowire(); - this.mvc.perform(get("/hello")).andExpect(status().isIAmATeapot()); - this.mvc.perform(get("/goodbye")).andExpect(status().isIAmATeapot()); - } - - @Configuration - @EnableWebSecurity - static class RequestMatcherBasedAccessDeniedHandlerConfig { - - AccessDeniedHandler teapotDeniedHandler = (request, response, exception) -> response - .setStatus(HttpStatus.I_AM_A_TEAPOT.value()); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().denyAll()) - .exceptionHandling((handling) -> handling - .defaultAccessDeniedHandlerFor( - this.teapotDeniedHandler, - pathPattern("/hello/**")) - .defaultAccessDeniedHandlerFor( - new AccessDeniedHandlerImpl(), - AnyRequestMatcher.INSTANCE)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class RequestMatcherBasedAccessDeniedHandlerInLambdaConfig { - - AccessDeniedHandler teapotDeniedHandler = (request, response, exception) -> response - .setStatus(HttpStatus.I_AM_A_TEAPOT.value()); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().denyAll() - ) - .exceptionHandling((exceptionHandling) -> exceptionHandling - .defaultAccessDeniedHandlerFor( - this.teapotDeniedHandler, - pathPattern("/hello/**") - ) - .defaultAccessDeniedHandlerFor( - new AccessDeniedHandlerImpl(), - AnyRequestMatcher.INSTANCE - ) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class SingleRequestMatcherAccessDeniedHandlerConfig { - - AccessDeniedHandler teapotDeniedHandler = (request, response, exception) -> response - .setStatus(HttpStatus.I_AM_A_TEAPOT.value()); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().denyAll()) - .exceptionHandling((handling) -> handling - .defaultAccessDeniedHandlerFor( - this.teapotDeniedHandler, - pathPattern("/hello/**"))); - return http.build(); - // @formatter:on - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.java deleted file mode 100644 index c25936c1657..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ExceptionHandlingConfigurerTests.java +++ /dev/null @@ -1,362 +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.annotation.web.configurers; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContextChangedListener; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.access.ExceptionTranslationFilter; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.accept.ContentNegotiationStrategy; -import org.springframework.web.context.request.NativeWebRequest; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link ExceptionHandlingConfigurer} - * - * @author Rob Winch - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class ExceptionHandlingConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnExceptionTranslationFilter() { - this.spring.register(ObjectPostProcessorConfig.class, DefaultSecurityConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(ExceptionTranslationFilter.class)); - } - - // SEC-2199 - @Test - public void getWhenAcceptHeaderIsApplicationXhtmlXmlThenRespondsWith302() throws Exception { - this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_XHTML_XML)) - .andExpect(status().isFound()); - } - - // SEC-2199 - @Test - public void getWhenAcceptHeaderIsImageGifThenRespondsWith302() throws Exception { - this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.IMAGE_GIF)).andExpect(status().isFound()); - } - - // SEC-2199 - @Test - public void getWhenAcceptHeaderIsImageJpgThenRespondsWith302() throws Exception { - this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.IMAGE_JPEG)).andExpect(status().isFound()); - } - - // SEC-2199 - @Test - public void getWhenAcceptHeaderIsImagePngThenRespondsWith302() throws Exception { - this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.IMAGE_PNG)).andExpect(status().isFound()); - } - - // SEC-2199 - @Test - public void getWhenAcceptHeaderIsTextHtmlThenRespondsWith302() throws Exception { - this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML)).andExpect(status().isFound()); - } - - // SEC-2199 - @Test - public void getWhenAcceptHeaderIsTextPlainThenRespondsWith302() throws Exception { - this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.TEXT_PLAIN)).andExpect(status().isFound()); - } - - // SEC-2199 - @Test - public void getWhenAcceptHeaderIsApplicationAtomXmlThenRespondsWith401() throws Exception { - this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_ATOM_XML)) - .andExpect(status().isUnauthorized()); - } - - // SEC-2199 - @Test - public void getWhenAcceptHeaderIsApplicationFormUrlEncodedThenRespondsWith401() throws Exception { - this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_FORM_URLENCODED)) - .andExpect(status().isUnauthorized()); - } - - // SEC-2199 - @Test - public void getWhenAcceptHeaderIsApplicationJsonThenRespondsWith401() throws Exception { - this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)) - .andExpect(status().isUnauthorized()); - } - - // SEC-2199 - @Test - public void getWhenAcceptHeaderIsApplicationOctetStreamThenRespondsWith401() throws Exception { - this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_OCTET_STREAM)) - .andExpect(status().isUnauthorized()); - } - - // SEC-2199 - @Test - public void getWhenAcceptHeaderIsMultipartFormDataThenRespondsWith401() throws Exception { - this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.MULTIPART_FORM_DATA)) - .andExpect(status().isUnauthorized()); - } - - // SEC-2199 - @Test - public void getWhenAcceptHeaderIsTextXmlThenRespondsWith401() throws Exception { - this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.TEXT_XML)).andExpect(status().isUnauthorized()); - } - - // gh-4831 - @Test - public void getWhenAcceptIsAnyThenRespondsWith401() throws Exception { - this.spring.register(DefaultSecurityConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, MediaType.ALL)).andExpect(status().isUnauthorized()); - } - - @Test - public void getWhenAcceptIsChromeThenRespondsWith302() throws Exception { - this.spring.register(DefaultSecurityConfig.class).autowire(); - this.mvc - .perform(get("/").header(HttpHeaders.ACCEPT, - "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")) - .andExpect(status().isFound()); - } - - @Test - public void getWhenAcceptIsTextPlainAndXRequestedWithIsXHRThenRespondsWith401() throws Exception { - this.spring.register(HttpBasicAndFormLoginEntryPointsConfig.class).autowire(); - this.mvc.perform(get("/").header("Accept", MediaType.TEXT_PLAIN).header("X-Requested-With", "XMLHttpRequest")) - .andExpect(status().isUnauthorized()); - } - - @Test - public void getWhenCustomContentNegotiationStrategyThenStrategyIsUsed() throws Exception { - this.spring.register(OverrideContentNegotiationStrategySharedObjectConfig.class, DefaultSecurityConfig.class) - .autowire(); - this.mvc.perform(get("/")); - verify(OverrideContentNegotiationStrategySharedObjectConfig.CNS, atLeastOnce()) - .resolveMediaTypes(any(NativeWebRequest.class)); - } - - @Test - public void getWhenCustomSecurityContextHolderStrategyThenUsed() throws Exception { - this.spring.register(SecurityContextChangedListenerConfig.class, DefaultSecurityConfig.class).autowire(); - this.mvc.perform(get("/")); - SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); - verify(strategy, atLeastOnce()).getContext(); - SecurityContextChangedListener listener = this.spring.getContext() - .getBean(SecurityContextChangedListener.class); - verify(listener).securityContextChanged(setAuthentication(AnonymousAuthenticationToken.class)); - } - - @Test - public void getWhenUsingDefaultsAndUnauthenticatedThenRedirectsToLogin() throws Exception { - this.spring.register(DefaultHttpConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, "bogus/type")).andExpect(redirectedUrl("/login")); - } - - @Test - public void getWhenDeclaringHttpBasicBeforeFormLoginThenRespondsWith401() throws Exception { - this.spring.register(BasicAuthenticationEntryPointBeforeFormLoginConfig.class).autowire(); - this.mvc.perform(get("/").header(HttpHeaders.ACCEPT, "bogus/type")).andExpect(status().isUnauthorized()); - } - - @Test - public void getWhenInvokingExceptionHandlingTwiceThenOriginalEntryPointUsed() throws Exception { - this.spring.register(InvokeTwiceDoesNotOverrideConfig.class).autowire(); - this.mvc.perform(get("/")); - verify(InvokeTwiceDoesNotOverrideConfig.AEP).commence(any(HttpServletRequest.class), - any(HttpServletResponse.class), any(AuthenticationException.class)); - } - - @Configuration - @EnableWebSecurity - static class ObjectPostProcessorConfig { - - static ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .exceptionHandling(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - static ObjectPostProcessor objectPostProcessor() { - return objectPostProcessor; - } - - } - - static class ReflectingObjectPostProcessor implements ObjectPostProcessor { - - @Override - public O postProcess(O object) { - return object; - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultSecurityConfig { - - @Bean - InMemoryUserDetailsManager userDetailsManager() { - // @formatter:off - return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build() - ); - // @formatter:off - } - } - @Configuration - @EnableWebSecurity - static class HttpBasicAndFormLoginEntryPointsConfig { - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .httpBasic(withDefaults()) - .formLogin(withDefaults()); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - static class OverrideContentNegotiationStrategySharedObjectConfig { - - static ContentNegotiationStrategy CNS = mock(ContentNegotiationStrategy.class); - - @Bean - static ContentNegotiationStrategy cns() { - return CNS; - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultHttpConfig { - - } - - @Configuration - @EnableWebSecurity - static class BasicAuthenticationEntryPointBeforeFormLoginConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .httpBasic(withDefaults()) - .formLogin(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class InvokeTwiceDoesNotOverrideConfig { - - static AuthenticationEntryPoint AEP = mock(AuthenticationEntryPoint.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .exceptionHandling((handling) -> handling - .authenticationEntryPoint(AEP)) - .exceptionHandling(withDefaults()); - return http.build(); - // @formatter:on - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java deleted file mode 100644 index 3bee5bc0d4c..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/FormLoginConfigurerTests.java +++ /dev/null @@ -1,904 +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.annotation.web.configurers; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.authorization.AllAuthoritiesAuthorizationManager; -import org.springframework.security.authorization.AuthenticatedAuthorizationManager; -import org.springframework.security.authorization.AuthorityAuthorizationManager; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.authorization.AuthorizationManagers; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.config.users.AuthenticationTestConfiguration; -import org.springframework.security.core.authority.FactorGrantedAuthority; -import org.springframework.security.core.context.SecurityContextChangedListener; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders; -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; -import org.springframework.security.web.PortMapper; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.access.ExceptionTranslationFilter; -import org.springframework.security.web.access.intercept.RequestAuthorizationContext; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler; -import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler; -import org.springframework.security.web.savedrequest.RequestCache; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.logout; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Rob Winch - * @author Eleftheria Stein - * @since 5.1 - */ -@ExtendWith(SpringTestContextExtension.class) -public class FormLoginConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mockMvc; - - @Test - public void requestCache() throws Exception { - this.spring.register(RequestCacheConfig.class, AuthenticationTestConfiguration.class).autowire(); - RequestCacheConfig config = this.spring.getContext().getBean(RequestCacheConfig.class); - this.mockMvc.perform(formLogin()).andExpect(authenticated()); - verify(config.requestCache).getRequest(any(), any()); - } - - @Test - public void requestCacheAsBean() throws Exception { - this.spring.register(RequestCacheBeanConfig.class, AuthenticationTestConfiguration.class).autowire(); - RequestCache requestCache = this.spring.getContext().getBean(RequestCache.class); - this.mockMvc.perform(formLogin()).andExpect(authenticated()); - verify(requestCache).getRequest(any(), any()); - } - - @Test - public void loginWhenFormLoginConfiguredThenHasDefaultUsernameAndPasswordParameterNames() throws Exception { - this.spring.register(FormLoginConfig.class).autowire(); - // @formatter:off - SecurityMockMvcRequestBuilders.FormLoginRequestBuilder loginRequest = formLogin() - .user("username", "user") - .password("password", "password"); - this.mockMvc.perform(loginRequest) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/")); - // @formatter:on - } - - @Test - public void formLoginWhenSecurityContextHolderStrategyThenUses() throws Exception { - this.spring.register(FormLoginConfig.class, SecurityContextChangedListenerConfig.class).autowire(); - // @formatter:off - SecurityMockMvcRequestBuilders.FormLoginRequestBuilder loginRequest = formLogin() - .user("username", "user") - .password("password", "password"); - this.mockMvc.perform(loginRequest) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/")); - // @formatter:on - SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); - verify(strategy, atLeastOnce()).getContext(); - SecurityContextChangedListener listener = this.spring.getContext() - .getBean(SecurityContextChangedListener.class); - verify(listener).securityContextChanged(setAuthentication(UsernamePasswordAuthenticationToken.class)); - } - - @Test - public void loginWhenFormLoginConfiguredThenHasDefaultFailureUrl() throws Exception { - this.spring.register(FormLoginConfig.class).autowire(); - // @formatter:off - this.mockMvc.perform(formLogin().user("invalid")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?error")); - // @formatter:on - } - - @Test - public void loginWhenFormLoginConfiguredThenHasDefaultSuccessUrl() throws Exception { - this.spring.register(FormLoginConfig.class).autowire(); - // @formatter:off - this.mockMvc.perform(formLogin()) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/")); - // @formatter:on - } - - @Test - public void getLoginPageWhenFormLoginConfiguredThenNotSecured() throws Exception { - this.spring.register(FormLoginConfig.class).autowire(); - this.mockMvc.perform(get("/login")).andExpect(status().isFound()); - } - - @Test - public void loginWhenFormLoginConfiguredThenSecured() throws Exception { - this.spring.register(FormLoginConfig.class).autowire(); - this.mockMvc.perform(post("/login")).andExpect(status().isForbidden()); - } - - @Test - public void requestProtectedWhenFormLoginConfiguredThenRedirectsToLogin() throws Exception { - this.spring.register(FormLoginConfig.class).autowire(); - // @formatter:off - this.mockMvc.perform(get("/private")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); - // @formatter:on - } - - @Test - public void loginWhenFormLoginDefaultsInLambdaThenHasDefaultUsernameAndPasswordParameterNames() throws Exception { - this.spring.register(FormLoginInLambdaConfig.class).autowire(); - // @formatter:off - SecurityMockMvcRequestBuilders.FormLoginRequestBuilder loginRequest = formLogin() - .user("username", "user") - .password("password", "password"); - this.mockMvc.perform(loginRequest) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/")); - // @formatter:on - } - - @Test - public void loginWhenFormLoginDefaultsInLambdaThenHasDefaultFailureUrl() throws Exception { - this.spring.register(FormLoginInLambdaConfig.class).autowire(); - // @formatter:off - this.mockMvc.perform(formLogin().user("invalid")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?error")); - // @formatter:on - } - - @Test - public void loginWhenFormLoginDefaultsInLambdaThenHasDefaultSuccessUrl() throws Exception { - this.spring.register(FormLoginInLambdaConfig.class).autowire(); - // @formatter:off - this.mockMvc.perform(formLogin()) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/")); - // @formatter:on - } - - @Test - public void getLoginPageWhenFormLoginDefaultsInLambdaThenNotSecured() throws Exception { - this.spring.register(FormLoginInLambdaConfig.class).autowire(); - this.mockMvc.perform(get("/login")).andExpect(status().isOk()); - } - - @Test - public void loginWhenFormLoginDefaultsInLambdaThenSecured() throws Exception { - this.spring.register(FormLoginInLambdaConfig.class).autowire(); - this.mockMvc.perform(post("/login")).andExpect(status().isForbidden()); - } - - @Test - public void requestProtectedWhenFormLoginDefaultsInLambdaThenRedirectsToLogin() throws Exception { - this.spring.register(FormLoginInLambdaConfig.class).autowire(); - // @formatter:off - this.mockMvc.perform(get("/private")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); - // @formatter:on - } - - @Test - public void getLoginPageWhenFormLoginPermitAllThenPermittedAndNoRedirect() throws Exception { - this.spring.register(FormLoginConfigPermitAll.class).autowire(); - // @formatter:off - this.mockMvc.perform(get("/login")) - .andExpect(status().isOk()) - .andExpect(redirectedUrl(null)); - // @formatter:on - } - - @Test - public void getLoginPageWithErrorQueryWhenFormLoginPermitAllThenPermittedAndNoRedirect() throws Exception { - this.spring.register(FormLoginConfigPermitAll.class).autowire(); - // @formatter:off - this.mockMvc.perform(get("/login?error")) - .andExpect(status().isOk()) - .andExpect(redirectedUrl(null)); - // @formatter:on - } - - @Test - public void loginWhenFormLoginPermitAllAndInvalidUserThenRedirectsToLoginPageWithError() throws Exception { - this.spring.register(FormLoginConfigPermitAll.class).autowire(); - // @formatter:off - this.mockMvc.perform(formLogin().user("invalid")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?error")); - // @formatter:on - } - - @Test - public void getLoginPageWhenCustomLoginPageThenPermittedAndNoRedirect() throws Exception { - this.spring.register(FormLoginDefaultsConfig.class).autowire(); - this.mockMvc.perform(get("/authenticate")).andExpect(redirectedUrl(null)); - } - - @Test - public void getLoginPageWithErrorQueryWhenCustomLoginPageThenPermittedAndNoRedirect() throws Exception { - this.spring.register(FormLoginDefaultsConfig.class).autowire(); - this.mockMvc.perform(get("/authenticate?error")).andExpect(redirectedUrl(null)); - } - - @Test - public void loginWhenCustomLoginPageAndInvalidUserThenRedirectsToCustomLoginPageWithError() throws Exception { - this.spring.register(FormLoginDefaultsConfig.class).autowire(); - SecurityMockMvcRequestBuilders.FormLoginRequestBuilder request = formLogin("/authenticate").user("invalid"); - // @formatter:off - this.mockMvc.perform(request) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/authenticate?error")); - // @formatter:on - } - - @Test - public void logoutWhenCustomLoginPageThenRedirectsToCustomLoginPage() throws Exception { - this.spring.register(FormLoginDefaultsConfig.class).autowire(); - this.mockMvc.perform(logout()).andExpect(redirectedUrl("/authenticate?logout")); - } - - @Test - public void getLoginPageWithLogoutQueryWhenCustomLoginPageThenPermittedAndNoRedirect() throws Exception { - this.spring.register(FormLoginDefaultsConfig.class).autowire(); - this.mockMvc.perform(get("/authenticate?logout")).andExpect(redirectedUrl(null)); - } - - @Test - public void getLoginPageWhenCustomLoginPageInLambdaThenPermittedAndNoRedirect() throws Exception { - this.spring.register(FormLoginDefaultsInLambdaConfig.class).autowire(); - this.mockMvc.perform(get("/authenticate")).andExpect(redirectedUrl(null)); - } - - @Test - public void loginWhenCustomLoginProcessingUrlThenRedirectsToHome() throws Exception { - this.spring.register(FormLoginLoginProcessingUrlConfig.class).autowire(); - // @formatter:off - this.mockMvc.perform(formLogin("/loginCheck")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/")); - // @formatter:on - } - - @Test - public void loginWhenCustomLoginProcessingUrlInLambdaThenRedirectsToHome() throws Exception { - this.spring.register(FormLoginLoginProcessingUrlInLambdaConfig.class).autowire(); - // @formatter:off - this.mockMvc.perform(formLogin("/loginCheck")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/")); - // @formatter:on - } - - @Test - public void requestWhenCustomPortMapperThenPortMapperUsed() throws Exception { - FormLoginUsesPortMapperConfig.PORT_MAPPER = mock(PortMapper.class); - given(FormLoginUsesPortMapperConfig.PORT_MAPPER.lookupHttpsPort(any())).willReturn(9443); - this.spring.register(FormLoginUsesPortMapperConfig.class).autowire(); - // @formatter:off - this.mockMvc.perform(get("http://localhost:9090")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("https://localhost:9443/login")); - // @formatter:on - verify(FormLoginUsesPortMapperConfig.PORT_MAPPER).lookupHttpsPort(any()); - } - - @Test - public void failureUrlWhenPermitAllAndFailureHandlerThenSecured() throws Exception { - this.spring.register(PermitAllIgnoresFailureHandlerConfig.class).autowire(); - // @formatter:off - this.mockMvc.perform(get("/login?error")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); - // @formatter:on - } - - @Test - public void formLoginWhenInvokedTwiceThenUsesOriginalUsernameParameter() throws Exception { - this.spring.register(DuplicateInvocationsDoesNotOverrideConfig.class).autowire(); - SecurityMockMvcRequestBuilders.FormLoginRequestBuilder loginRequest = formLogin().user("custom-username", - "user"); - this.mockMvc.perform(loginRequest).andExpect(authenticated()); - } - - @Test - public void loginWhenInvalidLoginAndFailureForwardUrlThenForwardsToFailureForwardUrl() throws Exception { - this.spring.register(FormLoginUserForwardAuthenticationSuccessAndFailureConfig.class).autowire(); - SecurityMockMvcRequestBuilders.FormLoginRequestBuilder loginRequest = formLogin().user("invalid"); - this.mockMvc.perform(loginRequest).andExpect(forwardedUrl("/failure_forward_url")); - } - - @Test - public void loginWhenSuccessForwardUrlThenForwardsToSuccessForwardUrl() throws Exception { - this.spring.register(FormLoginUserForwardAuthenticationSuccessAndFailureConfig.class).autowire(); - this.mockMvc.perform(formLogin()).andExpect(forwardedUrl("/success_forward_url")); - } - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnUsernamePasswordAuthenticationFilter() { - ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor) - .postProcess(any(UsernamePasswordAuthenticationFilter.class)); - } - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnLoginUrlAuthenticationEntryPoint() { - ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(LoginUrlAuthenticationEntryPoint.class)); - } - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnExceptionTranslationFilter() { - ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(ExceptionTranslationFilter.class)); - } - - @Test - void requestWhenUnauthenticatedThenRequiresTwoSteps() throws Exception { - this.spring.register(MfaDslConfig.class, UserConfig.class).autowire(); - UserDetails user = PasswordEncodedUser.user(); - this.mockMvc.perform(get("/profile").with(user(user))) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl( - "/login?factor.type=password&factor.type=ott&factor.reason=missing&factor.reason=missing")); - this.mockMvc - .perform(post("/ott/generate").param("username", "rod") - .with(user(user)) - .with(SecurityMockMvcRequestPostProcessors.csrf())) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/ott/sent")); - this.mockMvc - .perform(post("/login").param("username", "rod") - .param("password", "password") - .with(SecurityMockMvcRequestPostProcessors.csrf())) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/")); - user = PasswordEncodedUser.withUserDetails(user) - .authorities("profile:read", FactorGrantedAuthority.OTT_AUTHORITY) - .build(); - this.mockMvc.perform(get("/profile").with(user(user))) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/login?factor.type=password&factor.reason=missing")); - user = PasswordEncodedUser.withUserDetails(user) - .authorities("profile:read", FactorGrantedAuthority.PASSWORD_AUTHORITY) - .build(); - this.mockMvc.perform(get("/profile").with(user(user))) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/login?factor.type=ott&factor.reason=missing")); - user = PasswordEncodedUser.withUserDetails(user) - .authorities("profile:read", FactorGrantedAuthority.PASSWORD_AUTHORITY, - FactorGrantedAuthority.OTT_AUTHORITY) - .build(); - this.mockMvc.perform(get("/profile").with(user(user))).andExpect(status().isNotFound()); - } - - @Test - void requestWhenUnauthenticatedX509ThenRequiresTwoSteps() throws Exception { - this.spring.register(MfaDslX509Config.class, UserConfig.class, BasicMfaController.class).autowire(); - this.mockMvc.perform(get("/profile")).andExpect(status().is3xxRedirection()); - this.mockMvc.perform(get("/profile").with(user(User.withUsername("rod").authorities("profile:read").build()))) - .andExpect(status().isForbidden()); - this.mockMvc.perform(get("/login")).andExpect(status().isOk()); - this.mockMvc.perform(get("/profile").with(SecurityMockMvcRequestPostProcessors.x509("rod.cer"))) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/login?factor.type=password&factor.reason=missing")); - this.mockMvc - .perform(post("/login").param("username", "rod") - .param("password", "password") - .with(SecurityMockMvcRequestPostProcessors.x509("rod.cer")) - .with(SecurityMockMvcRequestPostProcessors.csrf())) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/")); - UserDetails authorized = PasswordEncodedUser.withUsername("rod") - .authorities("profile:read", FactorGrantedAuthority.X509_AUTHORITY, - FactorGrantedAuthority.PASSWORD_AUTHORITY) - .build(); - this.mockMvc.perform(get("/profile").with(user(authorized))).andExpect(status().isOk()); - } - - @Configuration - @EnableWebSecurity - static class RequestCacheConfig { - - private RequestCache requestCache = mock(RequestCache.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .requestCache((cache) -> cache - .requestCache(this.requestCache)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class RequestCacheBeanConfig { - - @Bean - RequestCache requestCache() { - return mock(RequestCache.class); - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class FormLoginConfig { - - @Bean - WebSecurityCustomizer webSecurityCustomizer() { - return (web) -> web.ignoring().requestMatchers("/resources/**"); - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin((login) -> login - .loginPage("/login")); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class FormLoginInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().hasRole("USER") - ) - .formLogin(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class FormLoginConfigPermitAll { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin((login) -> login - .permitAll()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class FormLoginDefaultsConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin((login) -> login - .loginPage("/authenticate") - .permitAll()) - .logout((logout) -> logout - .permitAll()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class FormLoginDefaultsInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().hasRole("USER") - ) - .formLogin((formLogin) -> formLogin - .loginPage("/authenticate") - .permitAll() - ) - .logout(LogoutConfigurer::permitAll); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class FormLoginLoginProcessingUrlConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .formLogin((login) -> login - .loginProcessingUrl("/loginCheck") - .loginPage("/login") - .defaultSuccessUrl("/", true) - .passwordParameter("password") - .usernameParameter("username") - .permitAll()) - .logout((logout) -> logout - .logoutSuccessUrl("/login") - .logoutUrl("/logout") - .deleteCookies("JSESSIONID")); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class FormLoginLoginProcessingUrlInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .formLogin((formLogin) -> formLogin - .loginProcessingUrl("/loginCheck") - .loginPage("/login") - .defaultSuccessUrl("/", true) - .permitAll() - ) - .logout((logout) -> logout - .logoutSuccessUrl("/login") - .logoutUrl("/logout") - .deleteCookies("JSESSIONID") - ); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class FormLoginUsesPortMapperConfig { - - static PortMapper PORT_MAPPER; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin((login) -> login - .permitAll()) - .portMapper((mapper) -> mapper - .portMapper(PORT_MAPPER)); - // @formatter:on - LoginUrlAuthenticationEntryPoint authenticationEntryPoint = (LoginUrlAuthenticationEntryPoint) http - .getConfigurer(FormLoginConfigurer.class) - .getAuthenticationEntryPoint(); - authenticationEntryPoint.setForceHttps(true); - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - static class PermitAllIgnoresFailureHandlerConfig { - - static AuthenticationFailureHandler FAILURE_HANDLER = mock(AuthenticationFailureHandler.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin((login) -> login - .failureHandler(FAILURE_HANDLER) - .permitAll()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class DuplicateInvocationsDoesNotOverrideConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin((login) -> login - .usernameParameter("custom-username")) - .formLogin(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class FormLoginUserForwardAuthenticationSuccessAndFailureConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .csrf((csrf) -> csrf - .disable()) - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .formLogin((login) -> login - .failureForwardUrl("/failure_forward_url") - .successForwardUrl("/success_forward_url") - .permitAll()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class ObjectPostProcessorConfig { - - static ObjectPostProcessor objectPostProcessor; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .exceptionHandling(withDefaults()) - .formLogin(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - static ObjectPostProcessor objectPostProcessor() { - return objectPostProcessor; - } - - } - - static class ReflectingObjectPostProcessor implements ObjectPostProcessor { - - @Override - public O postProcess(O object) { - return object; - } - - } - - @Configuration - @EnableWebSecurity - static class MfaDslConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http, - AuthorizationManagerFactory authz) throws Exception { - // @formatter:off - http - .formLogin(Customizer.withDefaults()) - .oneTimeTokenLogin(Customizer.withDefaults()) - .authorizeHttpRequests((authorize) -> authorize - .requestMatchers("/profile").access(authz.hasAuthority("profile:read")) - .anyRequest().access(authz.authenticated()) - ); - return http.build(); - // @formatter:on - } - - @Bean - OneTimeTokenGenerationSuccessHandler tokenGenerationSuccessHandler() { - return new RedirectOneTimeTokenGenerationSuccessHandler("/ott/sent"); - } - - @Bean - AuthorizationManagerFactory authz() { - return new AuthorizationManagerFactory<>(FactorGrantedAuthority.PASSWORD_AUTHORITY, - FactorGrantedAuthority.OTT_AUTHORITY); - } - - } - - @Configuration - @EnableWebSecurity - @EnableMethodSecurity - static class MfaDslX509Config { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http, - AuthorizationManagerFactory authz) throws Exception { - // @formatter:off - http - .x509(Customizer.withDefaults()) - .formLogin(Customizer.withDefaults()) - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().access(authz.authenticated()) - ); - return http.build(); - // @formatter:on - } - - @Bean - AuthorizationManagerFactory authz() { - return new AuthorizationManagerFactory<>(FactorGrantedAuthority.X509_AUTHORITY, - FactorGrantedAuthority.PASSWORD_AUTHORITY); - } - - } - - @Configuration - static class UserConfig { - - @Bean - UserDetails rod() { - return PasswordEncodedUser.withUsername("rod").password("password").build(); - } - - @Bean - UserDetailsService users(UserDetails user) { - return new InMemoryUserDetailsManager(user); - } - - } - - @RestController - static class BasicMfaController { - - @GetMapping("/profile") - @PreAuthorize("@authz.hasAuthority('profile:read')") - String profile() { - return "profile"; - } - - } - - public static class AuthorizationManagerFactory { - - private final AuthorizationManager authorities; - - AuthorizationManagerFactory(String... authorities) { - this.authorities = AllAuthoritiesAuthorizationManager.hasAllAuthorities(authorities); - } - - public AuthorizationManager authenticated() { - AuthenticatedAuthorizationManager authenticated = AuthenticatedAuthorizationManager.authenticated(); - return AuthorizationManagers.allOf(new AuthorizationDecision(false), this.authorities, authenticated); - } - - public AuthorizationManager hasAuthority(String authority) { - AuthorityAuthorizationManager authorized = AuthorityAuthorizationManager.hasAuthority(authority); - return AuthorizationManagers.allOf(new AuthorizationDecision(false), this.authorities, authorized); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerEagerHeadersTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerEagerHeadersTests.java deleted file mode 100644 index e3bfb1d5b4d..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerEagerHeadersTests.java +++ /dev/null @@ -1,98 +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.annotation.web.configurers; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.web.SecurityFilterChain; -import org.springframework.security.web.header.HeaderWriterFilter; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; - -/** - * Tests for {@link HeadersConfigurer}. - * - * @author Ankur Pathak - */ -@ExtendWith(SpringTestContextExtension.class) -public class HeadersConfigurerEagerHeadersTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void requestWhenHeadersEagerlyConfiguredThenHeadersAreWritten() throws Exception { - this.spring.register(HeadersAtTheBeginningOfRequestConfig.class, HomeController.class).autowire(); - this.mvc.perform(get("/").secure(true)) - .andExpect(header().string("X-Content-Type-Options", "nosniff")) - .andExpect(header().string("X-Frame-Options", "DENY")) - .andExpect(header().string("Strict-Transport-Security", "max-age=31536000 ; includeSubDomains")) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")) - .andExpect(header().string(HttpHeaders.EXPIRES, "0")) - .andExpect(header().string(HttpHeaders.PRAGMA, "no-cache")) - .andExpect(header().string("X-XSS-Protection", "0")); - } - - @Configuration - @EnableWebSecurity - public static class HeadersAtTheBeginningOfRequestConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .addObjectPostProcessor(new ObjectPostProcessor() { - @Override - public HeaderWriterFilter postProcess(HeaderWriterFilter filter) { - filter.setShouldWriteHeadersEagerly(true); - return filter; - } - })); - return http.build(); - // @formatter:on - } - - } - - @RestController - private static class HomeController { - - @GetMapping("/") - String ok() { - return "ok"; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java deleted file mode 100644 index e81541f2885..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HeadersConfigurerTests.java +++ /dev/null @@ -1,1351 +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.annotation.web.configurers; - -import java.net.URI; -import java.util.LinkedHashMap; -import java.util.Map; - -import com.google.common.net.HttpHeaders; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.web.SecurityFilterChain; -import org.springframework.security.web.header.writers.CrossOriginEmbedderPolicyHeaderWriter; -import org.springframework.security.web.header.writers.CrossOriginOpenerPolicyHeaderWriter; -import org.springframework.security.web.header.writers.CrossOriginResourcePolicyHeaderWriter; -import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy; -import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter; -import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter.XFrameOptionsMode; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.ResultMatcher; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; - -/** - * Tests for {@link HeadersConfigurer}. - * - * @author Rob Winch - * @author Tim Ysewyn - * @author Joe Grandja - * @author Eddú Meléndez - * @author Vedran Pavic - * @author Eleftheria Stein - * @author Marcus Da Coregio - * @author Daniel Garnier-Moiroux - */ -@ExtendWith(SpringTestContextExtension.class) -public class HeadersConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void getWhenHeadersConfiguredThenDefaultHeadersInResponse() throws Exception { - this.spring.register(HeadersConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(header().string(HttpHeaders.X_CONTENT_TYPE_OPTIONS, "nosniff")) - .andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsMode.DENY.name())) - .andExpect(header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains")) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")) - .andExpect(header().string(HttpHeaders.EXPIRES, "0")) - .andExpect(header().string(HttpHeaders.PRAGMA, "no-cache")) - .andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "0")) - .andReturn(); - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactlyInAnyOrder( - HttpHeaders.X_CONTENT_TYPE_OPTIONS, HttpHeaders.X_FRAME_OPTIONS, HttpHeaders.STRICT_TRANSPORT_SECURITY, - HttpHeaders.CACHE_CONTROL, HttpHeaders.EXPIRES, HttpHeaders.PRAGMA, HttpHeaders.X_XSS_PROTECTION); - } - - @Test - public void getWhenHeadersConfiguredInLambdaThenDefaultHeadersInResponse() throws Exception { - this.spring.register(HeadersInLambdaConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(header().string(HttpHeaders.X_CONTENT_TYPE_OPTIONS, "nosniff")) - .andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsMode.DENY.name())) - .andExpect(header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains")) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")) - .andExpect(header().string(HttpHeaders.EXPIRES, "0")) - .andExpect(header().string(HttpHeaders.PRAGMA, "no-cache")) - .andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "0")) - .andReturn(); - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactlyInAnyOrder( - HttpHeaders.X_CONTENT_TYPE_OPTIONS, HttpHeaders.X_FRAME_OPTIONS, HttpHeaders.STRICT_TRANSPORT_SECURITY, - HttpHeaders.CACHE_CONTROL, HttpHeaders.EXPIRES, HttpHeaders.PRAGMA, HttpHeaders.X_XSS_PROTECTION); - } - - @Test - public void getWhenHeaderDefaultsDisabledAndContentTypeConfiguredThenOnlyContentTypeHeaderInResponse() - throws Exception { - this.spring.register(ContentTypeOptionsConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/")) - .andExpect(header().string(HttpHeaders.X_CONTENT_TYPE_OPTIONS, "nosniff")) - .andReturn(); - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_CONTENT_TYPE_OPTIONS); - } - - @Test - public void getWhenOnlyContentTypeConfiguredInLambdaThenOnlyContentTypeHeaderInResponse() throws Exception { - this.spring.register(ContentTypeOptionsInLambdaConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/")) - .andExpect(header().string(HttpHeaders.X_CONTENT_TYPE_OPTIONS, "nosniff")) - .andReturn(); - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_CONTENT_TYPE_OPTIONS); - } - - @Test - public void getWhenHeaderDefaultsDisabledAndFrameOptionsConfiguredThenOnlyFrameOptionsHeaderInResponse() - throws Exception { - this.spring.register(FrameOptionsConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/")) - .andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsMode.DENY.name())) - .andReturn(); - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_FRAME_OPTIONS); - } - - @Test - public void getWhenHeaderDefaultsDisabledAndHstsConfiguredThenOnlyStrictTransportSecurityHeaderInResponse() - throws Exception { - this.spring.register(HstsConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY, "max-age=31536000 ; includeSubDomains")) - .andReturn(); - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.STRICT_TRANSPORT_SECURITY); - } - - @Test - public void getWhenHeaderDefaultsDisabledAndCacheControlConfiguredThenCacheControlAndExpiresAndPragmaHeadersInResponse() - throws Exception { - this.spring.register(CacheControlConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")) - .andExpect(header().string(HttpHeaders.EXPIRES, "0")) - .andExpect(header().string(HttpHeaders.PRAGMA, "no-cache")) - .andReturn(); - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactlyInAnyOrder(HttpHeaders.CACHE_CONTROL, - HttpHeaders.EXPIRES, HttpHeaders.PRAGMA); - } - - @Test - public void getWhenOnlyCacheControlConfiguredInLambdaThenCacheControlAndExpiresAndPragmaHeadersInResponse() - throws Exception { - this.spring.register(CacheControlInLambdaConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate")) - .andExpect(header().string(HttpHeaders.EXPIRES, "0")) - .andExpect(header().string(HttpHeaders.PRAGMA, "no-cache")) - .andReturn(); - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactlyInAnyOrder(HttpHeaders.CACHE_CONTROL, - HttpHeaders.EXPIRES, HttpHeaders.PRAGMA); - } - - @Test - public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredThenOnlyXssProtectionHeaderInResponse() - throws Exception { - this.spring.register(XssProtectionConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "0")) - .andReturn(); - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION); - } - - @Test - public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredEnabledModeBlockThenOnlyXssProtectionHeaderInResponse() - throws Exception { - this.spring.register(XssProtectionValueEnabledModeBlockConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "1; mode=block")) - .andReturn(); - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION); - } - - @Test - public void getWhenOnlyXssProtectionConfiguredInLambdaThenOnlyXssProtectionHeaderInResponse() throws Exception { - this.spring.register(XssProtectionInLambdaConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "0")) - .andReturn(); - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION); - } - - @Test - public void getWhenHeaderDefaultsDisabledAndXssProtectionConfiguredValueEnabledModeBlockInLambdaThenOnlyXssProtectionHeaderInResponse() - throws Exception { - this.spring.register(XssProtectionValueEnabledModeBlockInLambdaConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(header().string(HttpHeaders.X_XSS_PROTECTION, "1; mode=block")) - .andReturn(); - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.X_XSS_PROTECTION); - } - - @Test - public void getWhenFrameOptionsSameOriginConfiguredThenFrameOptionsHeaderHasValueSameOrigin() throws Exception { - this.spring.register(HeadersCustomSameOriginConfig.class).autowire(); - this.mvc.perform(get("/").secure(true)) - .andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsMode.SAMEORIGIN.name())) - .andReturn(); - } - - @Test - public void getWhenFrameOptionsSameOriginConfiguredInLambdaThenFrameOptionsHeaderHasValueSameOrigin() - throws Exception { - this.spring.register(HeadersCustomSameOriginInLambdaConfig.class).autowire(); - this.mvc.perform(get("/").secure(true)) - .andExpect(header().string(HttpHeaders.X_FRAME_OPTIONS, XFrameOptionsMode.SAMEORIGIN.name())) - .andReturn(); - } - - @Test - public void getWhenHeaderDefaultsDisabledAndPublicHpkpWithNoPinThenNoHeadersInResponse() throws Exception { - this.spring.register(HpkpConfigNoPins.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/")).andReturn(); - assertThat(mvcResult.getResponse().getHeaderNames()).isEmpty(); - } - - @Test - public void getWhenSecureRequestAndHpkpWithPinThenPublicKeyPinsReportOnlyHeaderInResponse() throws Exception { - this.spring.register(HpkpConfig.class).autowire(); - ResultMatcher pinsReportOnly = header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY, - "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\""); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(pinsReportOnly) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY); - } - - @Test - public void getWhenInsecureRequestHeaderDefaultsDisabledAndHpkpWithPinThenNoHeadersInResponse() throws Exception { - this.spring.register(HpkpConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/")).andReturn(); - assertThat(mvcResult.getResponse().getHeaderNames()).isEmpty(); - } - - @Test - public void getWhenHpkpWithMultiplePinsThenPublicKeyPinsReportOnlyHeaderWithMultiplePinsInResponse() - throws Exception { - this.spring.register(HpkpConfigWithPins.class).autowire(); - ResultMatcher pinsReportOnly = header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY, - "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; pin-sha256=\"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=\""); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(pinsReportOnly) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY); - } - - @Test - public void getWhenHpkpWithCustomAgeThenPublicKeyPinsReportOnlyHeaderWithCustomAgeInResponse() throws Exception { - this.spring.register(HpkpConfigCustomAge.class).autowire(); - ResultMatcher pinsReportOnly = header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY, - "max-age=604800 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\""); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(pinsReportOnly) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY); - } - - @Test - public void getWhenHpkpWithReportOnlyFalseThenPublicKeyPinsHeaderInResponse() throws Exception { - this.spring.register(HpkpConfigTerminateConnection.class).autowire(); - ResultMatcher pins = header().string(HttpHeaders.PUBLIC_KEY_PINS, - "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\""); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(pins) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS); - } - - @Test - public void getWhenHpkpIncludeSubdomainThenPublicKeyPinsReportOnlyHeaderWithIncludeSubDomainsInResponse() - throws Exception { - this.spring.register(HpkpConfigIncludeSubDomains.class).autowire(); - ResultMatcher pinsReportOnly = header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY, - "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; includeSubDomains"); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(pinsReportOnly) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY); - } - - @Test - public void getWhenHpkpWithReportUriThenPublicKeyPinsReportOnlyHeaderWithReportUriInResponse() throws Exception { - this.spring.register(HpkpConfigWithReportURI.class).autowire(); - ResultMatcher pinsReportOnly = header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY, - "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; report-uri=\"https://example.net/pkp-report\""); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(pinsReportOnly) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY); - } - - @Test - public void getWhenHpkpWithReportUriAsStringThenPublicKeyPinsReportOnlyHeaderWithReportUriInResponse() - throws Exception { - this.spring.register(HpkpConfigWithReportURIAsString.class).autowire(); - ResultMatcher pinsReportOnly = header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY, - "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; report-uri=\"https://example.net/pkp-report\""); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(pinsReportOnly) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY); - } - - @Test - public void getWhenHpkpWithReportUriInLambdaThenPublicKeyPinsReportOnlyHeaderWithReportUriInResponse() - throws Exception { - this.spring.register(HpkpWithReportUriInLambdaConfig.class).autowire(); - ResultMatcher pinsReportOnly = header().string(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY, - "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; report-uri=\"https://example.net/pkp-report\""); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(pinsReportOnly) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.PUBLIC_KEY_PINS_REPORT_ONLY); - } - - @Test - public void getWhenContentSecurityPolicyConfiguredThenContentSecurityPolicyHeaderInResponse() throws Exception { - this.spring.register(ContentSecurityPolicyDefaultConfig.class).autowire(); - ResultMatcher csp = header().string(HttpHeaders.CONTENT_SECURITY_POLICY, "default-src 'self'"); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(csp) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CONTENT_SECURITY_POLICY); - } - - @Test - public void getWhenContentSecurityPolicyWithReportOnlyThenContentSecurityPolicyReportOnlyHeaderInResponse() - throws Exception { - this.spring.register(ContentSecurityPolicyReportOnlyConfig.class).autowire(); - ResultMatcher cspReportOnly = header().string(HttpHeaders.CONTENT_SECURITY_POLICY_REPORT_ONLY, - "default-src 'self'; script-src trustedscripts.example.com"); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(cspReportOnly) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()) - .containsExactly(HttpHeaders.CONTENT_SECURITY_POLICY_REPORT_ONLY); - } - - @Test - public void getWhenContentSecurityPolicyWithReportOnlyInLambdaThenContentSecurityPolicyReportOnlyHeaderInResponse() - throws Exception { - this.spring.register(ContentSecurityPolicyReportOnlyInLambdaConfig.class).autowire(); - ResultMatcher csp = header().string(HttpHeaders.CONTENT_SECURITY_POLICY_REPORT_ONLY, - "default-src 'self'; script-src trustedscripts.example.com"); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(csp) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()) - .containsExactly(HttpHeaders.CONTENT_SECURITY_POLICY_REPORT_ONLY); - } - - @Test - public void configureWhenContentSecurityPolicyEmptyThenException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(ContentSecurityPolicyInvalidConfig.class).autowire()) - .withRootCauseInstanceOf(IllegalArgumentException.class); - } - - @Test - public void configureWhenContentSecurityPolicyEmptyInLambdaThenException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(ContentSecurityPolicyInvalidInLambdaConfig.class).autowire()) - .withRootCauseInstanceOf(IllegalArgumentException.class); - } - - @Test - public void configureWhenContentSecurityPolicyNoPolicyDirectivesInLambdaThenDefaultHeaderValue() throws Exception { - this.spring.register(ContentSecurityPolicyNoDirectivesInLambdaConfig.class).autowire(); - ResultMatcher csp = header().string(HttpHeaders.CONTENT_SECURITY_POLICY, "default-src 'self'"); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(csp) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CONTENT_SECURITY_POLICY); - } - - @Test - public void getWhenReferrerPolicyConfiguredThenReferrerPolicyHeaderInResponse() throws Exception { - this.spring.register(ReferrerPolicyDefaultConfig.class).autowire(); - ResultMatcher referrerPolicy = header().string("Referrer-Policy", ReferrerPolicy.NO_REFERRER.getPolicy()); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(referrerPolicy) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Referrer-Policy"); - } - - @Test - public void getWhenReferrerPolicyInLambdaThenReferrerPolicyHeaderInResponse() throws Exception { - this.spring.register(ReferrerPolicyDefaultInLambdaConfig.class).autowire(); - ResultMatcher referrerPolicy = header().string("Referrer-Policy", ReferrerPolicy.NO_REFERRER.getPolicy()); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(referrerPolicy) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Referrer-Policy"); - } - - @Test - public void getWhenReferrerPolicyConfiguredWithCustomValueThenReferrerPolicyHeaderWithCustomValueInResponse() - throws Exception { - this.spring.register(ReferrerPolicyCustomConfig.class).autowire(); - ResultMatcher referrerPolicy = header().string("Referrer-Policy", ReferrerPolicy.SAME_ORIGIN.getPolicy()); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(referrerPolicy) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Referrer-Policy"); - } - - @Test - public void getWhenReferrerPolicyConfiguredWithCustomValueInLambdaThenCustomValueInResponse() throws Exception { - this.spring.register(ReferrerPolicyCustomInLambdaConfig.class).autowire(); - ResultMatcher referrerPolicy = header().string("Referrer-Policy", ReferrerPolicy.SAME_ORIGIN.getPolicy()); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(referrerPolicy) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Referrer-Policy"); - } - - @Test - public void getWhenFeaturePolicyConfiguredThenFeaturePolicyHeaderInResponse() throws Exception { - this.spring.register(FeaturePolicyConfig.class).autowire(); - ResultMatcher featurePolicy = header().string("Feature-Policy", "geolocation 'self'"); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(featurePolicy) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Feature-Policy"); - } - - @Test - public void configureWhenFeaturePolicyEmptyThenException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(FeaturePolicyInvalidConfig.class).autowire()) - .withRootCauseInstanceOf(IllegalArgumentException.class); - } - - @Test - public void getWhenPermissionsPolicyConfiguredThenPermissionsPolicyHeaderInResponse() throws Exception { - this.spring.register(PermissionsPolicyConfig.class).autowire(); - ResultMatcher permissionsPolicy = header().string("Permissions-Policy", "geolocation=(self)"); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(permissionsPolicy) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Permissions-Policy"); - } - - @Test - public void getWhenPermissionsPolicyConfiguredWithStringThenPermissionsPolicyHeaderInResponse() throws Exception { - this.spring.register(PermissionsPolicyStringConfig.class).autowire(); - ResultMatcher permissionsPolicy = header().string("Permissions-Policy", "geolocation=(self)"); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(permissionsPolicy) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly("Permissions-Policy"); - } - - @Test - public void configureWhenPermissionsPolicyEmptyThenException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(PermissionsPolicyInvalidConfig.class).autowire()) - .withRootCauseInstanceOf(IllegalArgumentException.class); - } - - @Test - public void configureWhenPermissionsPolicyStringEmptyThenException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(PermissionsPolicyInvalidStringConfig.class).autowire()) - .withRootCauseInstanceOf(IllegalArgumentException.class); - } - - @Test - public void getWhenHstsConfiguredWithPreloadThenStrictTransportSecurityHeaderWithPreloadInResponse() - throws Exception { - this.spring.register(HstsWithPreloadConfig.class).autowire(); - ResultMatcher hsts = header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY, - "max-age=31536000 ; includeSubDomains ; preload"); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(hsts) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.STRICT_TRANSPORT_SECURITY); - } - - @Test - public void getWhenHstsConfiguredWithPreloadInLambdaThenStrictTransportSecurityHeaderWithPreloadInResponse() - throws Exception { - this.spring.register(HstsWithPreloadInLambdaConfig.class).autowire(); - ResultMatcher hsts = header().string(HttpHeaders.STRICT_TRANSPORT_SECURITY, - "max-age=31536000 ; includeSubDomains ; preload"); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").secure(true)) - .andExpect(hsts) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.STRICT_TRANSPORT_SECURITY); - } - - @Test - public void getWhenCustomCrossOriginPoliciesInLambdaThenCrossOriginPolicyHeadersWithCustomValuesInResponse() - throws Exception { - this.spring.register(CrossOriginCustomPoliciesInLambdaConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/")) - .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY, "same-origin")) - .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, "require-corp")) - .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY, "same-origin")) - .andReturn(); - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY, - HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY); - } - - @Test - public void getWhenCustomCrossOriginPoliciesThenCrossOriginPolicyHeadersWithCustomValuesInResponse() - throws Exception { - this.spring.register(CrossOriginCustomPoliciesConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/")) - .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY, "same-origin")) - .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, "require-corp")) - .andExpect(header().string(HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY, "same-origin")) - .andReturn(); - assertThat(mvcResult.getResponse().getHeaderNames()).containsExactly(HttpHeaders.CROSS_ORIGIN_OPENER_POLICY, - HttpHeaders.CROSS_ORIGIN_EMBEDDER_POLICY, HttpHeaders.CROSS_ORIGIN_RESOURCE_POLICY); - } - - @Configuration - @EnableWebSecurity - static class HeadersConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HeadersInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ContentTypeOptionsConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .contentTypeOptions(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ContentTypeOptionsInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .contentTypeOptions(withDefaults()) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class FrameOptionsConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .frameOptions(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HstsConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .httpStrictTransportSecurity(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class CacheControlConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .cacheControl(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class CacheControlInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .cacheControl(withDefaults()) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class XssProtectionConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .xssProtection(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class XssProtectionValueEnabledModeBlockConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .xssProtection((xss) -> xss - .headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK))); - // @formatter:on - return http.build(); - } - - } - - @EnableWebSecurity - static class XssProtectionInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .xssProtection(withDefaults()) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class XssProtectionValueEnabledModeBlockInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .xssProtection((xXssConfig) -> xXssConfig.headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK) - ) - ); - // @formatter:on - return http.build(); - } - - } - - @EnableWebSecurity - static class HeadersCustomSameOriginConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .frameOptions((frameOptions) -> frameOptions.sameOrigin())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HeadersCustomSameOriginInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .frameOptions((frameOptionsConfig) -> frameOptionsConfig.sameOrigin()) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HpkpConfigNoPins { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .httpPublicKeyPinning(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HpkpConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .httpPublicKeyPinning((hpkp) -> hpkp - .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HpkpConfigWithPins { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - Map pins = new LinkedHashMap<>(); - pins.put("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=", "sha256"); - pins.put("E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=", "sha256"); - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .httpPublicKeyPinning((hpkp) -> hpkp.withPins(pins))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HpkpConfigCustomAge { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .httpPublicKeyPinning((hpkp) -> hpkp - .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=") - .maxAgeInSeconds(604800))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HpkpConfigTerminateConnection { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .httpPublicKeyPinning((hpkp) -> hpkp - .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=") - .reportOnly(false))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HpkpConfigIncludeSubDomains { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .httpPublicKeyPinning((hpkp) -> hpkp - .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=") - .includeSubDomains(true))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HpkpConfigWithReportURI { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .httpPublicKeyPinning((hpkp) -> hpkp - .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=") - .reportUri(URI.create("https://example.net/pkp-report")))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HpkpConfigWithReportURIAsString { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .httpPublicKeyPinning((hpkp) -> hpkp - .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=") - .reportUri("https://example.net/pkp-report"))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HpkpWithReportUriInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .httpPublicKeyPinning((hpkp) -> hpkp - .addSha256Pins("d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=") - .reportUri("https://example.net/pkp-report") - ) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ContentSecurityPolicyDefaultConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .contentSecurityPolicy((csp) -> csp.policyDirectives("default-src 'self'"))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ContentSecurityPolicyReportOnlyConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .contentSecurityPolicy((csp) -> csp - .policyDirectives("default-src 'self'; script-src trustedscripts.example.com") - .reportOnly())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ContentSecurityPolicyReportOnlyInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .contentSecurityPolicy((csp) -> csp - .policyDirectives("default-src 'self'; script-src trustedscripts.example.com") - .reportOnly() - ) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ContentSecurityPolicyInvalidConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .contentSecurityPolicy((csp) -> csp.policyDirectives(""))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ContentSecurityPolicyInvalidInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .contentSecurityPolicy((csp) -> csp.policyDirectives("") - ) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ContentSecurityPolicyNoDirectivesInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .contentSecurityPolicy(withDefaults()) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ReferrerPolicyDefaultConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .referrerPolicy(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ReferrerPolicyDefaultInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .referrerPolicy(Customizer.withDefaults()) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ReferrerPolicyCustomConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .referrerPolicy((referrer) -> referrer.policy(ReferrerPolicy.SAME_ORIGIN))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ReferrerPolicyCustomInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .referrerPolicy((referrerPolicy) -> referrerPolicy.policy(ReferrerPolicy.SAME_ORIGIN) - ) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class FeaturePolicyConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .featurePolicy("geolocation 'self'")); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class FeaturePolicyInvalidConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .featurePolicy("")); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class PermissionsPolicyConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .permissionsPolicy((permissionsPolicy) -> permissionsPolicy.policy("geolocation=(self)"))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class PermissionsPolicyStringConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .permissionsPolicy((permissions) -> permissions.policy("geolocation=(self)"))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class PermissionsPolicyInvalidConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .permissionsPolicy((permissionsPolicy) -> permissionsPolicy.policy(null))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class PermissionsPolicyInvalidStringConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .permissionsPolicy((permissions) -> permissions.policy(""))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HstsWithPreloadConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .httpStrictTransportSecurity((hsts) -> hsts.preload(true))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HstsWithPreloadInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .httpStrictTransportSecurity((hstsConfig) -> hstsConfig.preload(true)) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class CrossOriginCustomPoliciesInLambdaConfig { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http.headers((headers) -> headers - .defaultsDisabled() - .crossOriginOpenerPolicy((policy) -> policy - .policy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.SAME_ORIGIN) - ) - .crossOriginEmbedderPolicy((policy) -> policy - .policy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP) - ) - .crossOriginResourcePolicy((policy) -> policy - .policy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.SAME_ORIGIN) - ) - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - static class CrossOriginCustomPoliciesConfig { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http.headers((headers) -> headers - .defaultsDisabled() - .crossOriginOpenerPolicy((opener) -> opener - .policy(CrossOriginOpenerPolicyHeaderWriter.CrossOriginOpenerPolicy.SAME_ORIGIN)) - .crossOriginEmbedderPolicy((embedder) -> embedder - .policy(CrossOriginEmbedderPolicyHeaderWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP)) - .crossOriginResourcePolicy((resource) -> resource - .policy(CrossOriginResourcePolicyHeaderWriter.CrossOriginResourcePolicy.SAME_ORIGIN))); - // @formatter:on - return http.build(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.java deleted file mode 100644 index 7931773cdab..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.java +++ /dev/null @@ -1,484 +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.annotation.web.configurers; - -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationHandler; -import io.micrometer.observation.ObservationRegistry; -import io.micrometer.observation.ObservationTextPublisher; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationObservationContext; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.observation.SecurityObservationSettings; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextChangedListener; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; -import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link HttpBasicConfigurer} - * - * @author Rob Winch - * @author Eleftheria Stein - * @author Evgeniy Cheban - */ -@ExtendWith(SpringTestContextExtension.class) -public class HttpBasicConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnBasicAuthenticationFilter() { - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(BasicAuthenticationFilter.class)); - } - - @Test - public void httpBasicWhenUsingDefaultsInLambdaThenResponseIncludesBasicChallenge() throws Exception { - this.spring.register(DefaultsLambdaEntryPointConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isUnauthorized()) - .andExpect(header().string("WWW-Authenticate", "Basic realm=\"Realm\"")); - // @formatter:on - } - - // SEC-2198 - @Test - public void httpBasicWhenUsingDefaultsThenResponseIncludesBasicChallenge() throws Exception { - this.spring.register(DefaultsEntryPointConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isUnauthorized()) - .andExpect(header().string("WWW-Authenticate", "Basic realm=\"Realm\"")); - // @formatter:on - } - - @Test - public void httpBasicWhenUsingCustomAuthenticationEntryPointThenResponseIncludesBasicChallenge() throws Exception { - CustomAuthenticationEntryPointConfig.ENTRY_POINT = mock(AuthenticationEntryPoint.class); - this.spring.register(CustomAuthenticationEntryPointConfig.class).autowire(); - this.mvc.perform(get("/")); - verify(CustomAuthenticationEntryPointConfig.ENTRY_POINT).commence(any(HttpServletRequest.class), - any(HttpServletResponse.class), any(AuthenticationException.class)); - } - - @Test - public void httpBasicWhenInvokedTwiceThenUsesOriginalEntryPoint() throws Exception { - this.spring.register(DuplicateDoesNotOverrideConfig.class).autowire(); - this.mvc.perform(get("/")); - verify(DuplicateDoesNotOverrideConfig.ENTRY_POINT).commence(any(HttpServletRequest.class), - any(HttpServletResponse.class), any(AuthenticationException.class)); - } - - // SEC-3019 - @Test - public void httpBasicWhenRememberMeConfiguredThenSetsRememberMeCookie() throws Exception { - this.spring.register(BasicUsesRememberMeConfig.class, Home.class).autowire(); - MockHttpServletRequestBuilder rememberMeRequest = get("/").with(httpBasic("user", "password")) - .param("remember-me", "true"); - this.mvc.perform(rememberMeRequest).andExpect(cookie().exists("remember-me")); - } - - @Test - public void httpBasicWhenDefaultsThenAcceptsBasicCredentials() throws Exception { - this.spring.register(HttpBasic.class, Users.class, Home.class).autowire(); - this.mvc.perform(get("/").with(httpBasic("user", "password"))) - .andExpect(status().isOk()) - .andExpect(content().string("user")); - } - - @Test - public void httpBasicWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { - this.spring.register(HttpBasic.class, Users.class, Home.class, SecurityContextChangedListenerConfig.class) - .autowire(); - this.mvc.perform(get("/").with(httpBasic("user", "password"))) - .andExpect(status().isOk()) - .andExpect(content().string("user")); - SecurityContextChangedListener listener = this.spring.getContext() - .getBean(SecurityContextChangedListener.class); - verify(listener).securityContextChanged(setAuthentication(UsernamePasswordAuthenticationToken.class)); - } - - @Test - public void httpBasicWhenUsingCustomSecurityContextRepositoryThenUses() throws Exception { - this.spring.register(CustomSecurityContextRepositoryConfig.class, Users.class, Home.class).autowire(); - this.mvc.perform(get("/").with(httpBasic("user", "password"))) - .andExpect(status().isOk()) - .andExpect(content().string("user")); - verify(CustomSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPOSITORY) - .saveContext(any(SecurityContext.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void httpBasicWhenObservationRegistryThenObserves() throws Exception { - this.spring.register(HttpBasic.class, Users.class, Home.class, ObservationRegistryConfig.class).autowire(); - ObservationHandler handler = this.spring.getContext().getBean(ObservationHandler.class); - this.mvc.perform(get("/").with(httpBasic("user", "password"))) - .andExpect(status().isOk()) - .andExpect(content().string("user")); - ArgumentCaptor context = ArgumentCaptor.forClass(Observation.Context.class); - verify(handler, atLeastOnce()).onStart(context.capture()); - assertThat(context.getAllValues()).anyMatch((c) -> c instanceof AuthenticationObservationContext); - verify(handler, atLeastOnce()).onStop(context.capture()); - assertThat(context.getAllValues()).anyMatch((c) -> c instanceof AuthenticationObservationContext); - this.mvc.perform(get("/").with(httpBasic("user", "wrong"))).andExpect(status().isUnauthorized()); - verify(handler).onError(context.capture()); - assertThat(context.getValue()).isInstanceOf(AuthenticationObservationContext.class); - } - - @Test - public void httpBasicWhenExcludeAuthenticationObservationsThenUnobserved() throws Exception { - this.spring - .register(HttpBasic.class, Users.class, Home.class, ObservationRegistryConfig.class, - SelectableObservationsConfig.class) - .autowire(); - ObservationHandler handler = this.spring.getContext().getBean(ObservationHandler.class); - this.mvc.perform(get("/").with(httpBasic("user", "password"))) - .andExpect(status().isOk()) - .andExpect(content().string("user")); - ArgumentCaptor context = ArgumentCaptor.forClass(Observation.Context.class); - verify(handler, atLeastOnce()).onStart(context.capture()); - assertThat(context.getAllValues()).noneMatch((c) -> c instanceof AuthenticationObservationContext); - context = ArgumentCaptor.forClass(Observation.Context.class); - verify(handler, atLeastOnce()).onStop(context.capture()); - assertThat(context.getAllValues()).noneMatch((c) -> c instanceof AuthenticationObservationContext); - this.mvc.perform(get("/").with(httpBasic("user", "wrong"))).andExpect(status().isUnauthorized()); - verify(handler, never()).onError(any()); - } - - @Configuration - @EnableWebSecurity - static class ObjectPostProcessorConfig { - - static ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .httpBasic(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - static ObjectPostProcessor objectPostProcessor() { - return objectPostProcessor; - } - - } - - static class ReflectingObjectPostProcessor implements ObjectPostProcessor { - - @Override - public O postProcess(O object) { - return object; - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultsLambdaEntryPointConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .httpBasic(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultsEntryPointConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .httpBasic(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - } - - @Configuration - @EnableWebSecurity - static class CustomAuthenticationEntryPointConfig { - - static AuthenticationEntryPoint ENTRY_POINT; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .httpBasic((basic) -> basic - .authenticationEntryPoint(ENTRY_POINT)); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - } - - @Configuration - @EnableWebSecurity - static class DuplicateDoesNotOverrideConfig { - - static AuthenticationEntryPoint ENTRY_POINT = mock(AuthenticationEntryPoint.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .httpBasic((basic) -> basic - .authenticationEntryPoint(ENTRY_POINT)) - .httpBasic(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - } - - @EnableWebSecurity - @Configuration - static class BasicUsesRememberMeConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .httpBasic(withDefaults()) - .rememberMe(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager( - // @formatter:off - org.springframework.security.core.userdetails.User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build() - // @formatter:on - ); - } - - } - - @Configuration - @EnableWebSecurity - static class HttpBasic { - - @Bean - SecurityFilterChain web(HttpSecurity http) throws Exception { - http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .httpBasic(Customizer.withDefaults()); - - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - static class CustomSecurityContextRepositoryConfig { - - static final SecurityContextRepository SECURITY_CONTEXT_REPOSITORY = mock(SecurityContextRepository.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .httpBasic((basic) -> basic - .securityContextRepository(SECURITY_CONTEXT_REPOSITORY)); - // @formatter:on - return http.build(); - } - - } - - @Configuration - static class Users { - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager( - // @formatter:off - User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build() - // @formatter:on - ); - } - - } - - @EnableWebMvc - @RestController - static class Home { - - @GetMapping("/") - String home(@AuthenticationPrincipal UserDetails user) { - return user.getUsername(); - } - - } - - @Configuration - static class ObservationRegistryConfig { - - private final ObservationRegistry registry = ObservationRegistry.create(); - - private final ObservationHandler handler = spy(new ObservationTextPublisher()); - - @Bean - ObservationRegistry observationRegistry() { - return this.registry; - } - - @Bean - ObservationHandler observationHandler() { - return this.handler; - } - - @Bean - ObservationRegistryPostProcessor observationRegistryPostProcessor( - ObjectProvider> handler) { - return new ObservationRegistryPostProcessor(handler); - } - - } - - static class ObservationRegistryPostProcessor implements BeanPostProcessor { - - private final ObjectProvider> handler; - - ObservationRegistryPostProcessor(ObjectProvider> handler) { - this.handler = handler; - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof ObservationRegistry registry) { - registry.observationConfig().observationHandler(this.handler.getObject()); - } - return bean; - } - - } - - @Configuration - static class SelectableObservationsConfig { - - @Bean - SecurityObservationSettings observabilityDefaults() { - return SecurityObservationSettings.withDefaults().shouldObserveAuthentications(false).build(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityLogoutTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityLogoutTests.java deleted file mode 100644 index 81f8733ebdb..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityLogoutTests.java +++ /dev/null @@ -1,114 +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.annotation.web.configurers; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.web.servlet.TestMockHttpServletRequests.post; - -/** - * @author Rob Winch - * - */ -public class HttpSecurityLogoutTests { - - AnnotationConfigWebApplicationContext context; - - MockHttpServletResponse response; - - MockFilterChain chain; - - @Autowired - FilterChainProxy springSecurityFilterChain; - - @BeforeEach - public void setup() { - this.response = new MockHttpServletResponse(); - this.chain = new MockFilterChain(); - } - - @AfterEach - public void cleanup() { - if (this.context != null) { - this.context.close(); - } - } - - // SEC-2848 - @Test - public void clearAuthenticationFalse() throws Exception { - loadConfig(ClearAuthenticationFalseConfig.class); - SecurityContext currentContext = SecurityContextHolder.createEmptyContext(); - currentContext.setAuthentication(new TestingAuthenticationToken("user", "password", "ROLE_USER")); - MockHttpServletRequest request = post("/logout").build(); - request.getSession() - .setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, currentContext); - this.springSecurityFilterChain.doFilter(request, this.response, this.chain); - assertThat(currentContext.getAuthentication()).isNotNull(); - } - - public void loadConfig(Class... configs) { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(configs); - this.context.refresh(); - this.context.getAutowireCapableBeanFactory().autowireBean(this); - } - - @EnableWebSecurity - @Configuration - static class ClearAuthenticationFalseConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .csrf((csrf) -> csrf.disable()) - .logout((logout) -> logout - .clearAuthentication(false)); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityObservationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityObservationTests.java deleted file mode 100644 index f46c9571938..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityObservationTests.java +++ /dev/null @@ -1,115 +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.annotation.web.configurers; - -import java.util.Iterator; - -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationHandler; -import io.micrometer.observation.ObservationRegistry; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Josh Cummings - * - */ -@ExtendWith(SpringTestContextExtension.class) -public class HttpSecurityObservationTests { - - @Autowired - MockMvc mvc; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - public void getWhenUsingObservationRegistryThenObservesRequest() throws Exception { - this.spring.register(ObservationRegistryConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("/").with(httpBasic("user", "password"))) - .andExpect(status().isNotFound()); - // @formatter:on - ObservationHandler handler = this.spring.getContext().getBean(ObservationHandler.class); - ArgumentCaptor captor = ArgumentCaptor.forClass(Observation.Context.class); - verify(handler, times(5)).onStart(captor.capture()); - Iterator contexts = captor.getAllValues().iterator(); - assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain before"); - assertThat(contexts.next().getName()).isEqualTo("spring.security.authentications"); - assertThat(contexts.next().getName()).isEqualTo("spring.security.authorizations"); - assertThat(contexts.next().getName()).isEqualTo("spring.security.http.secured.requests"); - assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain after"); - } - - @EnableWebSecurity - @Configuration - static class ObservationRegistryConfig { - - private ObservationHandler handler = mock(ObservationHandler.class); - - @Bean - SecurityFilterChain app(HttpSecurity http) throws Exception { - http.httpBasic(withDefaults()).authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()); - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager( - User.withDefaultPasswordEncoder().username("user").password("password").authorities("app").build()); - } - - @Bean - ObservationHandler observationHandler() { - return this.handler; - } - - @Bean - ObservationRegistry observationRegistry() { - given(this.handler.supportsContext(any())).willReturn(true); - ObservationRegistry registry = ObservationRegistry.create(); - registry.observationConfig().observationHandler(this.handler); - return registry; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java deleted file mode 100644 index 1b710200a84..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecurityRequestMatchersTests.java +++ /dev/null @@ -1,453 +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.annotation.web.configurers; - -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockServletContext; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.web.servlet.TestMockHttpServletRequests.get; - -/** - * @author Rob Winch - * - */ -public class HttpSecurityRequestMatchersTests { - - AnnotationConfigWebApplicationContext context; - - MockHttpServletResponse response; - - MockFilterChain chain; - - @Autowired - FilterChainProxy springSecurityFilterChain; - - @BeforeEach - public void setup() { - this.response = new MockHttpServletResponse(); - this.chain = new MockFilterChain(); - } - - @AfterEach - public void cleanup() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void mvcMatcherGetFiltersNoUnsupportedMethodExceptionFromDummyRequest() { - loadConfig(MvcMatcherConfig.class); - assertThat(this.springSecurityFilterChain.getFilters("/path")).isNotEmpty(); - } - - @Test - public void requestMatchersMvcMatcherServletPath() throws Exception { - loadConfig(RequestMatchersMvcMatcherServeltPathConfig.class); - MockHttpServletRequest request = get().requestUri(null, "/spring", "/path").build(); - this.springSecurityFilterChain.doFilter(request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - request = get().requestUri(null, "", "/path").build(); - this.springSecurityFilterChain.doFilter(request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - setup(); - request = get().requestUri(null, "/other", "/path").build(); - this.springSecurityFilterChain.doFilter(request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - } - - @Test - public void requestMatcherWhensMvcMatcherServletPathInLambdaThenPathIsSecured() throws Exception { - loadConfig(RequestMatchersMvcMatcherServletPathInLambdaConfig.class); - MockHttpServletRequest request = get().requestUri(null, "/spring", "/path").build(); - this.springSecurityFilterChain.doFilter(request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - request = get().requestUri(null, "", "/path").build(); - this.springSecurityFilterChain.doFilter(request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - setup(); - request = get().requestUri(null, "/other", "/path").build(); - this.springSecurityFilterChain.doFilter(request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - } - - @Test - public void requestMatcherWhenMultiMvcMatcherInLambdaThenAllPathsAreDenied() throws Exception { - loadConfig(MultiMvcMatcherInLambdaConfig.class); - MockHttpServletRequest request = get("/test-1").build(); - this.springSecurityFilterChain.doFilter(request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - request = get("/test-2").build(); - this.springSecurityFilterChain.doFilter(request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - request = get("/test-3").build(); - this.springSecurityFilterChain.doFilter(request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - } - - @Test - public void requestMatcherWhenMultiMvcMatcherThenAllPathsAreDenied() throws Exception { - loadConfig(MultiMvcMatcherConfig.class); - MockHttpServletRequest request = get("/test-1").build(); - this.springSecurityFilterChain.doFilter(request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - request = get("/test-2").build(); - this.springSecurityFilterChain.doFilter(request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - request = get("/test-3").build(); - this.springSecurityFilterChain.doFilter(request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - } - - public void loadConfig(Class... configs) { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(configs); - this.context.setServletContext(new MockServletContext()); - this.context.refresh(); - this.context.getAutowireCapableBeanFactory().autowireBean(this); - } - - @EnableWebSecurity - @Configuration - @EnableWebMvc - static class MultiMvcMatcherInLambdaConfig { - - @Bean - PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { - return new PathPatternRequestMatcherBuilderFactoryBean(); - } - - @Bean - @Order(Ordered.HIGHEST_PRECEDENCE) - SecurityFilterChain first(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { - // @formatter:off - http - .securityMatchers((requests) -> requests - .requestMatchers(builder.matcher("/test-1")) - .requestMatchers(builder.matcher("/test-2")) - .requestMatchers(builder.matcher("/test-3")) - ) - .authorizeHttpRequests((authorize) -> authorize.anyRequest().denyAll()) - .httpBasic(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - SecurityFilterChain second(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { - // @formatter:off - http - .securityMatchers((requests) -> requests - .requestMatchers(builder.matcher("/test-1")) - ) - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().permitAll() - ); - // @formatter:on - return http.build(); - } - - @RestController - static class PathController { - - @RequestMapping({ "/test-1", "/test-2", "/test-3" }) - String path() { - return "path"; - } - - } - - } - - @EnableWebSecurity - @Configuration - @EnableWebMvc - static class MultiMvcMatcherConfig { - - @Bean - PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { - return new PathPatternRequestMatcherBuilderFactoryBean(); - } - - @Bean - @Order(Ordered.HIGHEST_PRECEDENCE) - SecurityFilterChain first(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { - // @formatter:off - http - .securityMatchers((security) -> security - .requestMatchers(builder.matcher("/test-1")) - .requestMatchers(builder.matcher("/test-2")) - .requestMatchers(builder.matcher("/test-3"))) - .authorizeHttpRequests((requests) -> requests - .anyRequest().denyAll()) - .httpBasic(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - SecurityFilterChain second(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { - // @formatter:off - http - .securityMatchers((security) -> security - .requestMatchers(builder.matcher("/test-1"))) - .authorizeHttpRequests((requests) -> requests - .anyRequest().permitAll()); - // @formatter:on - return http.build(); - } - - @RestController - static class PathController { - - @RequestMapping({ "/test-1", "/test-2", "/test-3" }) - String path() { - return "path"; - } - - } - - } - - @EnableWebSecurity - @Configuration - @EnableWebMvc - static class MvcMatcherConfig { - - @Bean - PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { - return new PathPatternRequestMatcherBuilderFactoryBean(); - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { - // @formatter:off - http - .securityMatcher(builder.matcher("/path")) - .httpBasic(withDefaults()) - .authorizeHttpRequests((requests) -> requests - .anyRequest().denyAll()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - @RestController - static class PathController { - - @RequestMapping("/path") - String path() { - return "path"; - } - - } - - } - - @EnableWebSecurity - @Configuration - @EnableWebMvc - static class RequestMatchersMvcMatcherConfig { - - @Bean - PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { - return new PathPatternRequestMatcherBuilderFactoryBean(); - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { - // @formatter:off - http - .securityMatchers((security) -> security - .requestMatchers(builder.matcher("/path"))) - .httpBasic(withDefaults()) - .authorizeHttpRequests((requests) -> requests - .anyRequest().denyAll()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - @RestController - static class PathController { - - @RequestMapping("/path") - String path() { - return "path"; - } - - } - - } - - @EnableWebSecurity - @Configuration - @EnableWebMvc - static class RequestMatchersMvcMatcherInLambdaConfig { - - @Bean - PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { - return new PathPatternRequestMatcherBuilderFactoryBean(); - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { - // @formatter:off - http - .securityMatchers((secure) -> secure - .requestMatchers(builder.matcher("/path")) - ) - .httpBasic(withDefaults()) - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().denyAll() - ); - return http.build(); - // @formatter:on - } - - @RestController - static class PathController { - - @RequestMapping("/path") - String path() { - return "path"; - } - - } - - } - - @EnableWebSecurity - @Configuration - @EnableWebMvc - static class RequestMatchersMvcMatcherServeltPathConfig { - - @Bean - PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { - return new PathPatternRequestMatcherBuilderFactoryBean(); - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { - // @formatter:off - http - .securityMatchers((security) -> security - .requestMatchers(builder.basePath("/spring").matcher("/path")) - .requestMatchers("/never-match")) - .httpBasic(withDefaults()) - .authorizeHttpRequests((requests) -> requests - .anyRequest().denyAll()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - @RestController - static class PathController { - - @RequestMapping("/path") - String path() { - return "path"; - } - - } - - } - - @EnableWebSecurity - @Configuration - @EnableWebMvc - static class RequestMatchersMvcMatcherServletPathInLambdaConfig { - - @Bean - PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { - return new PathPatternRequestMatcherBuilderFactoryBean(); - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { - // @formatter:off - http - .securityMatchers((secure) -> secure - .requestMatchers(builder.basePath("/spring").matcher("/path")) - .requestMatchers("/never-match") - ) - .httpBasic(withDefaults()) - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().denyAll() - ); - return http.build(); - // @formatter:on - } - - @RestController - static class PathController { - - @RequestMapping("/path") - String path() { - return "path"; - } - - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersNoMvcTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersNoMvcTests.java deleted file mode 100644 index 8d4e395d56e..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersNoMvcTests.java +++ /dev/null @@ -1,144 +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.annotation.web.configurers; - -import java.util.List; - -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -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.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockServletContext; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.test.support.ClassPathExclusions; -import org.springframework.security.web.DefaultSecurityFilterChain; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * @author Marcus Da Coregio - * - */ -@ClassPathExclusions("spring-webmvc-*.jar") -public class HttpSecuritySecurityMatchersNoMvcTests { - - AnnotationConfigWebApplicationContext context; - - MockHttpServletRequest request; - - MockHttpServletResponse response; - - MockFilterChain chain; - - @Autowired - FilterChainProxy springSecurityFilterChain; - - @BeforeEach - public void setup() throws Exception { - this.request = new MockHttpServletRequest(); - this.request.setMethod("GET"); - this.response = new MockHttpServletResponse(); - this.chain = new MockFilterChain(); - } - - @AfterEach - public void cleanup() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void securityMatcherWhenNoMvcThenAntMatcher() throws Exception { - loadConfig(SecurityMatcherNoMvcConfig.class); - this.request.setRequestURI("/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setRequestURI("/path.html"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - setup(); - this.request.setRequestURI("/path/"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - List requestMatchers = this.springSecurityFilterChain.getFilterChains() - .stream() - .map((chain) -> ((DefaultSecurityFilterChain) chain).getRequestMatcher()) - .map((matcher) -> ReflectionTestUtils.getField(matcher, "requestMatchers")) - .map((matchers) -> (List) matchers) - .findFirst() - .get(); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - assertThat(requestMatchers).hasOnlyElementsOfType(PathPatternRequestMatcher.class); - } - - public void loadConfig(Class... configs) { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(configs); - this.context.setServletContext(new MockServletContext()); - this.context.refresh(); - this.context.getAutowireCapableBeanFactory().autowireBean(this); - } - - @EnableWebSecurity - @Configuration - @Import(HttpSecuritySecurityMatchersTests.UsersConfig.class) - static class SecurityMatcherNoMvcConfig { - - @Bean - SecurityFilterChain appSecurity(HttpSecurity http) throws Exception { - // @formatter:off - http - .securityMatcher("/path") - .httpBasic(withDefaults()) - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().denyAll()); - // @formatter:on - return http.build(); - } - - @RestController - static class PathController { - - @RequestMapping("/path") - String path() { - return "path"; - } - - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersTests.java deleted file mode 100644 index 5502a6c1abb..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpSecuritySecurityMatchersTests.java +++ /dev/null @@ -1,448 +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.annotation.web.configurers; - -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.servlet.MockServletContext; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * @author Rob Winch - * - */ -public class HttpSecuritySecurityMatchersTests { - - AnnotationConfigWebApplicationContext context; - - MockHttpServletRequest request; - - MockHttpServletResponse response; - - MockFilterChain chain; - - @Autowired - FilterChainProxy springSecurityFilterChain; - - @BeforeEach - public void setup() throws Exception { - this.request = new MockHttpServletRequest(MockServletContext.mvc(), "GET", ""); - this.request.setMethod("GET"); - this.response = new MockHttpServletResponse(); - this.chain = new MockFilterChain(); - } - - @AfterEach - public void cleanup() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void securityMatcherWhenMvcMatcherAndGetFiltersNoUnsupportedMethodExceptionFromDummyRequest() { - loadConfig(SecurityMatcherMvcConfig.class); - assertThat(this.springSecurityFilterChain.getFilters("/path")).isNotEmpty(); - } - - @Test - public void securityMatchersMvcMatcherServletPath() throws Exception { - loadConfig(SecurityMatchersMvcMatcherServletPathConfig.class); - this.request.setServletPath("/spring"); - this.request.setRequestURI("/spring/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath(""); - this.request.setRequestURI("/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - setup(); - this.request.setServletPath("/other"); - this.request.setRequestURI("/other/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - } - - @Test - public void securityMatchersWhensMvcMatcherServletPathInLambdaThenPathIsSecured() throws Exception { - loadConfig(SecurityMatchersMvcMatcherServletPathInLambdaConfig.class); - this.request.setServletPath("/spring"); - this.request.setRequestURI("/spring/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setServletPath(""); - this.request.setRequestURI("/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - setup(); - this.request.setServletPath("/other"); - this.request.setRequestURI("/other/path"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - } - - @Test - public void securityMatchersWhenMultiMvcMatcherInLambdaThenAllPathsAreDenied() throws Exception { - loadConfig(MultiMvcMatcherInLambdaConfig.class); - this.request.setRequestURI("/test-1"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setRequestURI("/test-2"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setRequestURI("/test-3"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - } - - @Test - public void securityMatchersWhenMultiMvcMatcherThenAllPathsAreDenied() throws Exception { - loadConfig(MultiMvcMatcherConfig.class); - this.request.setRequestURI("/test-1"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setRequestURI("/test-2"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - setup(); - this.request.setRequestURI("/test-3"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - } - - public void loadConfig(Class... configs) { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(configs); - this.context.setServletContext(MockServletContext.mvc()); - this.context.refresh(); - this.context.getAutowireCapableBeanFactory().autowireBean(this); - } - - @EnableWebSecurity - @Configuration - @EnableWebMvc - static class MultiMvcMatcherInLambdaConfig { - - @Bean - @Order(Ordered.HIGHEST_PRECEDENCE) - SecurityFilterChain first(HttpSecurity http) throws Exception { - // @formatter:off - http - .securityMatchers((requests) -> requests - .requestMatchers("/test-1") - .requestMatchers("/test-2") - .requestMatchers("/test-3") - ) - .authorizeHttpRequests((authorize) -> authorize.anyRequest().denyAll()) - .httpBasic(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - SecurityFilterChain second(HttpSecurity http) throws Exception { - // @formatter:off - http - .securityMatchers((requests) -> requests - .requestMatchers("/test-1") - ) - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().permitAll() - ); - // @formatter:on - return http.build(); - } - - @RestController - static class PathController { - - @RequestMapping({ "/test-1", "/test-2", "/test-3" }) - String path() { - return "path"; - } - - } - - } - - @EnableWebSecurity - @Configuration - @EnableWebMvc - static class MultiMvcMatcherConfig { - - @Bean - @Order(Ordered.HIGHEST_PRECEDENCE) - SecurityFilterChain first(HttpSecurity http) throws Exception { - // @formatter:off - http - .securityMatchers((security) -> security - .requestMatchers("/test-1") - .requestMatchers("/test-2") - .requestMatchers("/test-3")) - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().denyAll()) - .httpBasic(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - SecurityFilterChain second(HttpSecurity http) throws Exception { - // @formatter:off - http - .securityMatchers((security) -> security - .requestMatchers("/test-1")) - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().permitAll()); - // @formatter:on - return http.build(); - } - - @RestController - static class PathController { - - @RequestMapping({ "/test-1", "/test-2", "/test-3" }) - String path() { - return "path"; - } - - } - - } - - @EnableWebSecurity - @EnableWebMvc - @Configuration - @Import(UsersConfig.class) - static class SecurityMatcherMvcConfig { - - @Bean - SecurityFilterChain appSecurity(HttpSecurity http) throws Exception { - // @formatter:off - http - .securityMatcher("/path") - .httpBasic(withDefaults()) - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().denyAll()); - // @formatter:on - return http.build(); - } - - @RestController - static class PathController { - - @RequestMapping("/path") - String path() { - return "path"; - } - - } - - } - - @EnableWebSecurity - @Configuration - @EnableWebMvc - @Import(UsersConfig.class) - static class SecurityMatchersMvcMatcherConfig { - - @Bean - SecurityFilterChain appSecurity(HttpSecurity http) throws Exception { - // @formatter:off - http - .securityMatcher("/path") - .httpBasic(withDefaults()) - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().denyAll()); - // @formatter:on - return http.build(); - } - - @RestController - static class PathController { - - @RequestMapping("/path") - String path() { - return "path"; - } - - } - - } - - @EnableWebSecurity - @Configuration - @EnableWebMvc - static class SecurityMatchersMvcMatcherInLambdaConfig { - - @Bean - SecurityFilterChain appSecurity(HttpSecurity http) throws Exception { - // @formatter:off - http - .securityMatchers((matchers) -> matchers - .requestMatchers("/path") - ) - .httpBasic(withDefaults()) - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().denyAll() - ); - // @formatter:on - return http.build(); - } - - @RestController - static class PathController { - - @RequestMapping("/path") - String path() { - return "path"; - } - - } - - } - - @EnableWebSecurity - @Configuration - @EnableWebMvc - @Import(UsersConfig.class) - static class SecurityMatchersMvcMatcherServletPathConfig { - - @Bean - PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { - PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean(); - bean.setBasePath("/spring"); - return bean; - } - - @Bean - SecurityFilterChain appSecurity(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { - // @formatter:off - http - .securityMatchers((security) -> security - .requestMatchers(builder.matcher("/path")) - .requestMatchers(builder.matcher("/never-match")) - ) - .httpBasic(withDefaults()) - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().denyAll()); - // @formatter:on - return http.build(); - } - - @RestController - static class PathController { - - @RequestMapping("/path") - String path() { - return "path"; - } - - } - - } - - @EnableWebSecurity - @Configuration - @EnableWebMvc - @Import(UsersConfig.class) - static class SecurityMatchersMvcMatcherServletPathInLambdaConfig { - - @Bean - PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { - PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean(); - bean.setBasePath("/spring"); - return bean; - } - - @Bean - SecurityFilterChain appSecurity(HttpSecurity http, PathPatternRequestMatcher.Builder builder) throws Exception { - // @formatter:off - http - .securityMatchers((matchers) -> matchers - .requestMatchers(builder.matcher("/path")) - .requestMatchers(builder.matcher("/never-match")) - ) - .httpBasic(withDefaults()) - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().denyAll() - ); - // @formatter:on - return http.build(); - } - - @RestController - static class PathController { - - @RequestMapping("/path") - String path() { - return "path"; - } - - } - - } - - @Configuration - static class UsersConfig { - - @Bean - UserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build(); - return new InMemoryUserDetailsManager(user); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpsRedirectConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpsRedirectConfigurerTests.java deleted file mode 100644 index 94274d64128..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpsRedirectConfigurerTests.java +++ /dev/null @@ -1,164 +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.annotation.web.configurers; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.config.web.PathPatternRequestMatcherBuilderFactoryBean; -import org.springframework.security.web.PortMapper; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link HttpsRedirectConfigurerTests} - * - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class HttpsRedirectConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void getWhenSecureThenDoesNotRedirect() throws Exception { - this.spring.register(RedirectToHttpConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("https://localhost")) - .andExpect(status().isNotFound()); - // @formatter:on - } - - @Test - public void getWhenInsecureThenRespondsWithRedirectToSecure() throws Exception { - this.spring.register(RedirectToHttpConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("http://localhost")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("https://localhost")); - // @formatter:on - } - - @Test - public void getWhenInsecureAndPathRequiresTransportSecurityThenRedirects() throws Exception { - this.spring.register(SometimesRedirectToHttpsConfig.class, UsePathPatternConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("http://localhost:8080")) - .andExpect(status().isNotFound()); - this.mvc.perform(get("http://localhost:8080/secure")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("https://localhost:8443/secure")); - // @formatter:on - } - - @Test - public void getWhenInsecureAndUsingCustomPortMapperThenRespondsWithRedirectToSecurePort() throws Exception { - this.spring.register(RedirectToHttpsViaCustomPortsConfig.class).autowire(); - PortMapper portMapper = this.spring.getContext().getBean(PortMapper.class); - given(portMapper.lookupHttpsPort(4080)).willReturn(4443); - // @formatter:off - this.mvc.perform(get("http://localhost:4080")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("https://localhost:4443")); - // @formatter:on - } - - @Configuration - @EnableWebMvc - @EnableWebSecurity - static class RedirectToHttpConfig { - - @Bean - SecurityFilterChain springSecurity(HttpSecurity http) throws Exception { - // @formatter:off - http - .redirectToHttps(withDefaults()); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebMvc - @EnableWebSecurity - static class SometimesRedirectToHttpsConfig { - - @Bean - SecurityFilterChain springSecurity(HttpSecurity http, PathPatternRequestMatcher.Builder path) throws Exception { - // @formatter:off - http - .redirectToHttps((https) -> https.requestMatchers(path.matcher("/secure"))); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebMvc - @EnableWebSecurity - static class RedirectToHttpsViaCustomPortsConfig { - - @Bean - SecurityFilterChain springSecurity(HttpSecurity http) throws Exception { - // @formatter:off - http - .portMapper((p) -> p.portMapper(portMapper())) - .redirectToHttps(withDefaults()); - - // @formatter:on - return http.build(); - } - - @Bean - PortMapper portMapper() { - return mock(PortMapper.class); - } - - } - - @Configuration - static class UsePathPatternConfig { - - @Bean - PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() { - return new PathPatternRequestMatcherBuilderFactoryBean(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/JeeConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/JeeConfigurerTests.java deleted file mode 100644 index f30cd581bdf..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/JeeConfigurerTests.java +++ /dev/null @@ -1,275 +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.annotation.web.configurers; - -import java.security.Principal; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.authority.AuthorityUtils; -import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; -import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource; -import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; - -/** - * Tests for {@link JeeConfigurer} - * - * @author Rob Winch - * @author Eleftheria Stein - */ -@ExtendWith(SpringTestContextExtension.class) -public class JeeConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnJ2eePreAuthenticatedProcessingFilter() { - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - ObjectPostProcessor objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class); - verify(objectPostProcessor).postProcess(any(J2eePreAuthenticatedProcessingFilter.class)); - } - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnJ2eeBasedPreAuthenticatedWebAuthenticationDetailsSource() { - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - ObjectPostProcessor objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class); - verify(objectPostProcessor).postProcess(any(J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource.class)); - } - - @Test - public void jeeWhenInvokedTwiceThenUsesOriginalMappableRoles() throws Exception { - this.spring.register(InvokeTwiceDoesNotOverride.class).autowire(); - Principal user = mock(Principal.class); - given(user.getName()).willReturn("user"); - // @formatter:off - MockHttpServletRequestBuilder authRequest = get("/") - .principal(user) - .with((request) -> { - request.addUserRole("ROLE_ADMIN"); - request.addUserRole("ROLE_USER"); - return request; - }); - // @formatter:on - this.mvc.perform(authRequest).andExpect(authenticated().withRoles("USER")); - } - - @Test - public void requestWhenJeeMappableRolesInLambdaThenAuthenticatedWithMappableRoles() throws Exception { - this.spring.register(JeeMappableRolesConfig.class).autowire(); - Principal user = mock(Principal.class); - given(user.getName()).willReturn("user"); - // @formatter:off - MockHttpServletRequestBuilder authRequest = get("/") - .principal(user) - .with((request) -> { - request.addUserRole("ROLE_ADMIN"); - request.addUserRole("ROLE_USER"); - return request; - }); - // @formatter:on - this.mvc.perform(authRequest).andExpect(authenticated().withRoles("USER")); - } - - @Test - public void requestWhenJeeMappableAuthoritiesInLambdaThenAuthenticatedWithMappableAuthorities() throws Exception { - this.spring.register(JeeMappableAuthoritiesConfig.class).autowire(); - Principal user = mock(Principal.class); - given(user.getName()).willReturn("user"); - // @formatter:off - MockHttpServletRequestBuilder authRequest = get("/") - .principal(user) - .with((request) -> { - request.addUserRole("ROLE_ADMIN"); - request.addUserRole("ROLE_USER"); - return request; - }); - // @formatter:on - SecurityMockMvcResultMatchers.AuthenticatedMatcher authenticatedAsUser = authenticated() - .withAuthorities(AuthorityUtils.createAuthorityList("ROLE_USER")); - this.mvc.perform(authRequest).andExpect(authenticatedAsUser); - } - - @Test - public void requestWhenCustomAuthenticatedUserDetailsServiceInLambdaThenCustomAuthenticatedUserDetailsServiceUsed() - throws Exception { - this.spring.register(JeeCustomAuthenticatedUserDetailsServiceConfig.class).autowire(); - AuthenticationUserDetailsService userDetailsService = this.spring - .getContext() - .getBean(AuthenticationUserDetailsService.class); - Principal user = mock(Principal.class); - User userDetails = new User("user", "N/A", true, true, true, true, - AuthorityUtils.createAuthorityList("ROLE_USER")); - given(user.getName()).willReturn("user"); - given(userDetailsService.loadUserDetails(any())).willReturn(userDetails); - // @formatter:off - MockHttpServletRequestBuilder authRequest = get("/") - .principal(user) - .with((request) -> { - request.addUserRole("ROLE_ADMIN"); - request.addUserRole("ROLE_USER"); - return request; - }); - // @formatter:on - this.mvc.perform(authRequest).andExpect(authenticated().withRoles("USER")); - } - - @Configuration - @EnableWebSecurity - static class ObjectPostProcessorConfig { - - ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .jee(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - @Primary - ObjectPostProcessor objectPostProcessor() { - return this.objectPostProcessor; - } - - } - - static class ReflectingObjectPostProcessor implements ObjectPostProcessor { - - @Override - public O postProcess(O object) { - return object; - } - - } - - @Configuration - @EnableWebSecurity - static class InvokeTwiceDoesNotOverride { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .jee((jee) -> jee - .mappableRoles("USER")) - .jee(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - public static class JeeMappableRolesConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().hasRole("USER") - ) - .jee((jee) -> jee - .mappableRoles("USER") - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - public static class JeeMappableAuthoritiesConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().hasRole("USER") - ) - .jee((jee) -> jee - .mappableAuthorities("ROLE_USER") - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - public static class JeeCustomAuthenticatedUserDetailsServiceConfig { - - private AuthenticationUserDetailsService authenticationUserDetailsService = mock( - AuthenticationUserDetailsService.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().hasRole("USER") - ) - .jee((jee) -> jee - .authenticatedUserDetailsService(this.authenticationUserDetailsService) - ); - return http.build(); - // @formatter:on - } - - @Bean - AuthenticationUserDetailsService authenticationUserDetailsService() { - return this.authenticationUserDetailsService; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerClearSiteDataTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerClearSiteDataTests.java deleted file mode 100644 index ab72c1561e8..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerClearSiteDataTests.java +++ /dev/null @@ -1,108 +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.annotation.web.configurers; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.logout.HeaderWriterLogoutHandler; -import org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter; -import org.springframework.security.web.header.writers.ClearSiteDataHeaderWriter.Directive; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; - -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; - -/** - * - * Tests for {@link HeaderWriterLogoutHandler} that passing - * {@link ClearSiteDataHeaderWriter} implementation. - * - * @author Rafiullah Hamedy - * - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class LogoutConfigurerClearSiteDataTests { - - private static final String CLEAR_SITE_DATA_HEADER = "Clear-Site-Data"; - - private static final Directive[] SOURCE = { Directive.CACHE, Directive.COOKIES, Directive.STORAGE, - Directive.EXECUTION_CONTEXTS }; - - private static final String HEADER_VALUE = "\"cache\", \"cookies\", \"storage\", \"executionContexts\""; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - @WithMockUser - public void logoutWhenRequestTypeGetThenHeaderNotPresentt() throws Exception { - this.spring.register(HttpLogoutConfig.class).autowire(); - MockHttpServletRequestBuilder logoutRequest = get("/logout").secure(true).with(csrf()); - this.mvc.perform(logoutRequest).andExpect(header().doesNotExist(CLEAR_SITE_DATA_HEADER)); - } - - @Test - @WithMockUser - public void logoutWhenRequestTypePostAndNotSecureThenHeaderNotPresent() throws Exception { - this.spring.register(HttpLogoutConfig.class).autowire(); - MockHttpServletRequestBuilder logoutRequest = post("/logout").with(csrf()); - this.mvc.perform(logoutRequest).andExpect(header().doesNotExist(CLEAR_SITE_DATA_HEADER)); - } - - @Test - @WithMockUser - public void logoutWhenRequestTypePostAndSecureThenHeaderIsPresent() throws Exception { - this.spring.register(HttpLogoutConfig.class).autowire(); - MockHttpServletRequestBuilder logoutRequest = post("/logout").secure(true).with(csrf()); - this.mvc.perform(logoutRequest).andExpect(header().stringValues(CLEAR_SITE_DATA_HEADER, HEADER_VALUE)); - } - - @Configuration - @EnableWebSecurity - static class HttpLogoutConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .logout((logout) -> logout - .addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(SOURCE)))); - return http.build(); - // @formatter:on - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.java deleted file mode 100644 index 79cd3b04596..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/LogoutConfigurerTests.java +++ /dev/null @@ -1,650 +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.annotation.web.configurers; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.apache.http.HttpHeaders; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.context.SecurityContextHolderStrategy; -import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.RememberMeServices; -import org.springframework.security.web.authentication.logout.LogoutFilter; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link LogoutConfigurer} - * - * @author Rob Winch - * @author Eleftheria Stein - */ -@ExtendWith(SpringTestContextExtension.class) -public class LogoutConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void configureWhenDefaultLogoutSuccessHandlerForHasNullLogoutHandlerThenException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(NullLogoutSuccessHandlerConfig.class).autowire()) - .withRootCauseInstanceOf(IllegalArgumentException.class); - } - - @Test - public void configureWhenDefaultLogoutSuccessHandlerForHasNullLogoutHandlerInLambdaThenException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(NullLogoutSuccessHandlerInLambdaConfig.class).autowire()) - .withRootCauseInstanceOf(IllegalArgumentException.class); - } - - @Test - public void configureWhenDefaultLogoutSuccessHandlerForHasNullMatcherThenException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(NullMatcherConfig.class).autowire()) - .withRootCauseInstanceOf(IllegalArgumentException.class); - } - - @Test - public void configureWhenDefaultLogoutSuccessHandlerForHasNullMatcherInLambdaThenException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(NullMatcherInLambdaConfig.class).autowire()) - .withRootCauseInstanceOf(IllegalArgumentException.class); - } - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnLogoutFilter() { - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - ObjectPostProcessor objectPostProcessor = this.spring.getContext() - .getBean(ObjectPostProcessorConfig.class).objectPostProcessor; - verify(objectPostProcessor).postProcess(any(LogoutFilter.class)); - } - - @Test - public void logoutWhenInvokedTwiceThenUsesOriginalLogoutUrl() throws Exception { - this.spring.register(DuplicateDoesNotOverrideConfig.class).autowire(); - MockHttpServletRequestBuilder logoutRequest = post("/custom/logout").with(csrf()); - // @formatter:off - this.mvc.perform(logoutRequest) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - // @formatter:on - } - - // SEC-2311 - @Test - public void logoutWhenGetRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception { - this.spring.register(CsrfDisabledConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("/logout")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - // @formatter:on - } - - @Test - public void logoutWhenPostRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception { - this.spring.register(CsrfDisabledConfig.class).autowire(); - // @formatter:off - this.mvc.perform(post("/logout")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - // @formatter:on - } - - @Test - public void logoutWhenPutRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception { - this.spring.register(CsrfDisabledConfig.class).autowire(); - // @formatter:off - this.mvc.perform(put("/logout")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - // @formatter:on - } - - @Test - public void logoutWhenDeleteRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception { - this.spring.register(CsrfDisabledConfig.class).autowire(); - // @formatter:off - this.mvc.perform(delete("/logout")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - // @formatter:on - } - - @Test - public void logoutWhenGetRequestAndCsrfDisabledAndCustomLogoutUrlThenRedirectsToLogin() throws Exception { - this.spring.register(CsrfDisabledAndCustomLogoutConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("/custom/logout")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - // @formatter:on - } - - @Test - public void logoutWhenPostRequestAndCsrfDisabledAndCustomLogoutUrlThenRedirectsToLogin() throws Exception { - this.spring.register(CsrfDisabledAndCustomLogoutConfig.class).autowire(); - // @formatter:off - this.mvc.perform(post("/custom/logout")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - // @formatter:on - } - - @Test - public void logoutWhenPutRequestAndCsrfDisabledAndCustomLogoutUrlThenRedirectsToLogin() throws Exception { - this.spring.register(CsrfDisabledAndCustomLogoutConfig.class).autowire(); - // @formatter:off - this.mvc.perform(put("/custom/logout")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - // @formatter:on - } - - @Test - public void logoutWhenDeleteRequestAndCsrfDisabledAndCustomLogoutUrlThenRedirectsToLogin() throws Exception { - this.spring.register(CsrfDisabledAndCustomLogoutConfig.class).autowire(); - // @formatter:off - this.mvc.perform(delete("/custom/logout")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - // @formatter:on - } - - @Test - public void logoutWhenCustomLogoutUrlInLambdaThenRedirectsToLogin() throws Exception { - this.spring.register(CsrfDisabledAndCustomLogoutInLambdaConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("/custom/logout")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - // @formatter:on - } - - // SEC-3170 - @Test - public void configureWhenLogoutHandlerNullThenException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(NullLogoutHandlerConfig.class).autowire()) - .withRootCauseInstanceOf(IllegalArgumentException.class); - } - - @Test - public void configureWhenLogoutHandlerNullInLambdaThenException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(NullLogoutHandlerInLambdaConfig.class).autowire()) - .withRootCauseInstanceOf(IllegalArgumentException.class); - } - - // SEC-3170 - @Test - public void rememberMeWhenRememberMeServicesNotLogoutHandlerThenRedirectsToLogin() throws Exception { - this.spring.register(RememberMeNoLogoutHandler.class).autowire(); - this.mvc.perform(post("/logout").with(csrf())) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - } - - @Test - public void logoutWhenAcceptTextHtmlThenRedirectsToLogin() throws Exception { - this.spring.register(BasicSecurityConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder logoutRequest = post("/logout") - .with(csrf()) - .with(user("user")) - .header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); - this.mvc.perform(logoutRequest) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - // @formatter:on - } - - @Test - public void logoutWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { - this.spring.register(BasicSecurityConfig.class, SecurityContextChangedListenerConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder logoutRequest = post("/logout") - .with(csrf()) - .with(user("user")) - .header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); - this.mvc.perform(logoutRequest) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - // @formatter:on - SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); - verify(strategy, atLeastOnce()).getContext(); - } - - // gh-3282 - @Test - public void logoutWhenAcceptApplicationJsonThenReturnsStatusNoContent() throws Exception { - this.spring.register(BasicSecurityConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = post("/logout") - .with(csrf()) - .with(user("user")) - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE); - // @formatter:on - this.mvc.perform(request).andExpect(status().isNoContent()); - } - - // gh-4831 - @Test - public void logoutWhenAcceptAllThenReturnsStatusNoContent() throws Exception { - this.spring.register(BasicSecurityConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder logoutRequest = post("/logout") - .with(csrf()) - .with(user("user")) - .header(HttpHeaders.ACCEPT, MediaType.ALL_VALUE); - // @formatter:on - this.mvc.perform(logoutRequest).andExpect(status().isNoContent()); - } - - // gh-3902 - @Test - public void logoutWhenAcceptFromChromeThenRedirectsToLogin() throws Exception { - this.spring.register(BasicSecurityConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = post("/logout") - .with(csrf()) - .with(user("user")) - .header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"); - this.mvc.perform(request) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - // @formatter:on - } - - // gh-3997 - @Test - public void logoutWhenXMLHttpRequestThenReturnsStatusNoContent() throws Exception { - this.spring.register(BasicSecurityConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = post("/logout") - .with(csrf()) - .with(user("user")) - .header(HttpHeaders.ACCEPT, "text/html,application/json") - .header("X-Requested-With", "XMLHttpRequest"); - // @formatter:on - this.mvc.perform(request).andExpect(status().isNoContent()); - } - - @Test - public void logoutWhenDisabledThenLogoutUrlNotFound() throws Exception { - this.spring.register(LogoutDisabledConfig.class).autowire(); - this.mvc.perform(post("/logout").with(csrf())).andExpect(status().isNotFound()); - } - - @Test - public void logoutWhenCustomSecurityContextRepositoryThenUses() throws Exception { - CustomSecurityContextRepositoryConfig.repository = mock(SecurityContextRepository.class); - this.spring.register(CustomSecurityContextRepositoryConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder logoutRequest = post("/logout") - .with(csrf()) - .with(user("user")) - .header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); - this.mvc.perform(logoutRequest) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - // @formatter:on - int invocationCount = 2; // 1 from user() post processor and 1 from - // SecurityContextLogoutHandler - verify(CustomSecurityContextRepositoryConfig.repository, times(invocationCount)).saveContext(any(), - any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void logoutWhenNoSecurityContextRepositoryThenHttpSessionSecurityContextRepository() throws Exception { - this.spring.register(InvalidateHttpSessionFalseConfig.class).autowire(); - MockHttpSession session = mock(MockHttpSession.class); - // @formatter:off - MockHttpServletRequestBuilder logoutRequest = post("/logout") - .with(csrf()) - .with(user("user")) - .session(session) - .header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); - this.mvc.perform(logoutRequest) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")) - .andReturn(); - // @formatter:on - verify(session).removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); - } - - @Configuration - @EnableWebSecurity - static class InvalidateHttpSessionFalseConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .logout((logout) -> logout.invalidateHttpSession(false)) - .securityContext((context) -> context.requireExplicitSave(true)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class CustomSecurityContextRepositoryConfig { - - static SecurityContextRepository repository; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .logout(Customizer.withDefaults()) - .securityContext((context) -> context - .requireExplicitSave(true) - .securityContextRepository(repository) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class NullLogoutSuccessHandlerConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .logout((logout) -> logout - .defaultLogoutSuccessHandlerFor(null, mock(RequestMatcher.class))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class NullLogoutSuccessHandlerInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .logout((logout) -> logout.defaultLogoutSuccessHandlerFor(null, mock(RequestMatcher.class)) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class NullMatcherConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .logout((logout) -> logout - .defaultLogoutSuccessHandlerFor(mock(LogoutSuccessHandler.class), null)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class NullMatcherInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .logout((logout) -> logout.defaultLogoutSuccessHandlerFor(mock(LogoutSuccessHandler.class), null) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ObjectPostProcessorConfig { - - ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .logout(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - ObjectPostProcessor objectPostProcessor() { - return this.objectPostProcessor; - } - - } - - static class ReflectingObjectPostProcessor implements ObjectPostProcessor { - - @Override - public O postProcess(O object) { - return object; - } - - } - - @Configuration - @EnableWebSecurity - static class DuplicateDoesNotOverrideConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .logout((logout) -> logout - .logoutUrl("/custom/logout")) - .logout(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class CsrfDisabledConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .csrf((csrf) -> csrf - .disable()) - .logout(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class CsrfDisabledAndCustomLogoutConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .csrf((csrf) -> csrf - .disable()) - .logout((logout) -> logout - .logoutUrl("/custom/logout")); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class CsrfDisabledAndCustomLogoutInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .csrf((csrf) -> csrf - .disable()) - .logout((logout) -> logout.logoutUrl("/custom/logout")); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class NullLogoutHandlerConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .logout((logout) -> logout - .addLogoutHandler(null)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class NullLogoutHandlerInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .logout((logout) -> logout.addLogoutHandler(null)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class RememberMeNoLogoutHandler { - - static RememberMeServices REMEMBER_ME = mock(RememberMeServices.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .rememberMe((me) -> me - .rememberMeServices(REMEMBER_ME)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class BasicSecurityConfig { - - } - - @Configuration - @EnableWebSecurity - static class LogoutDisabledConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .logout((logout) -> logout - .disable()); - return http.build(); - // @formatter:on - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceDebugTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceDebugTests.java deleted file mode 100644 index ecb0b6279cd..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceDebugTests.java +++ /dev/null @@ -1,98 +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.annotation.web.configurers; - -import ch.qos.logback.classic.Level; -import ch.qos.logback.classic.Logger; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.Appender; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.slf4j.LoggerFactory; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Configuration; -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.web.debug.DebugFilter; -import org.springframework.test.web.servlet.MockMvc; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; - -/** - * Tests to verify {@code EnableWebSecurity(debug)} functionality - * - * @author Rob Winch - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class NamespaceDebugTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void requestWhenDebugSetToTrueThenLogsDebugInformation() throws Exception { - Appender appender = mockAppenderFor("Spring Security Debugger"); - this.spring.register(DebugWebSecurity.class).autowire(); - this.mvc.perform(get("/")); - verify(appender, atLeastOnce()).doAppend(any(ILoggingEvent.class)); - } - - @Test - public void requestWhenDebugSetToFalseThenDoesNotLogDebugInformation() throws Exception { - Appender appender = mockAppenderFor("Spring Security Debugger"); - this.spring.register(NoDebugWebSecurity.class).autowire(); - this.mvc.perform(get("/")); - assertThat(filterChainClass()).isNotEqualTo(DebugFilter.class); - verify(appender, never()).doAppend(any(ILoggingEvent.class)); - } - - private Appender mockAppenderFor(String name) { - Appender appender = mock(Appender.class); - Logger logger = (Logger) LoggerFactory.getLogger(name); - logger.setLevel(Level.DEBUG); - logger.addAppender(appender); - return appender; - } - - private Class filterChainClass() { - return this.spring.getContext().getBean("springSecurityFilterChain").getClass(); - } - - @Configuration - @EnableWebSecurity(debug = true) - static class DebugWebSecurity { - - } - - @Configuration - @EnableWebSecurity - static class NoDebugWebSecurity { - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpAnonymousTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpAnonymousTests.java deleted file mode 100644 index 3579a658eb7..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpAnonymousTests.java +++ /dev/null @@ -1,217 +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.annotation.web.configurers; - -import java.util.Optional; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests to verify that all the functionality of <anonymous> attributes is present - * - * @author Rob Winch - * @author Josh Cummings - * - */ -@ExtendWith(SpringTestContextExtension.class) -public class NamespaceHttpAnonymousTests { - - @Autowired - MockMvc mvc; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - public void anonymousRequestWhenUsingDefaultAnonymousConfigurationThenUsesAnonymousAuthentication() - throws Exception { - this.spring.register(AnonymousConfig.class, AnonymousController.class).autowire(); - this.mvc.perform(get("/type")).andExpect(content().string(AnonymousAuthenticationToken.class.getSimpleName())); - } - - @Test - public void anonymousRequestWhenDisablingAnonymousThenDenies() throws Exception { - this.spring.register(AnonymousDisabledConfig.class, AnonymousController.class).autowire(); - this.mvc.perform(get("/type")).andExpect(status().isForbidden()); - } - - @Test - public void requestWhenAnonymousThenSendsAnonymousConfiguredAuthorities() throws Exception { - this.spring.register(AnonymousGrantedAuthorityConfig.class, AnonymousController.class).autowire(); - this.mvc.perform(get("/type")).andExpect(content().string(AnonymousAuthenticationToken.class.getSimpleName())); - } - - @Test - public void anonymousRequestWhenAnonymousKeyConfiguredThenKeyIsUsed() throws Exception { - this.spring.register(AnonymousKeyConfig.class, AnonymousController.class).autowire(); - this.mvc.perform(get("/key")).andExpect(content().string(String.valueOf("AnonymousKeyConfig".hashCode()))); - } - - @Test - public void anonymousRequestWhenAnonymousUsernameConfiguredThenUsernameIsUsed() throws Exception { - this.spring.register(AnonymousUsernameConfig.class, AnonymousController.class).autowire(); - this.mvc.perform(get("/principal")).andExpect(content().string("AnonymousUsernameConfig")); - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class AnonymousConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .requestMatchers("/type").anonymous() - .anyRequest().denyAll()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class AnonymousDisabledConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests.anyRequest().anonymous()) - .anonymous((anonymous) -> anonymous.disable()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin()); - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class AnonymousGrantedAuthorityConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .requestMatchers("/type").hasRole("ANON") - .anyRequest().denyAll()) - .anonymous((anonymous) -> anonymous - .authorities("ROLE_ANON")); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class AnonymousKeyConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .requestMatchers("/key").anonymous() - .anyRequest().denyAll()) - .anonymous((anonymous) -> anonymous.key("AnonymousKeyConfig")); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class AnonymousUsernameConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .requestMatchers("/principal").anonymous() - .anyRequest().denyAll()) - .anonymous((anonymous) -> anonymous.principal("AnonymousUsernameConfig")); - return http.build(); - // @formatter:on - } - - } - - @RestController - static class AnonymousController { - - @GetMapping("/type") - String type() { - return anonymousToken().map(AnonymousAuthenticationToken::getClass).map(Class::getSimpleName).orElse(null); - } - - @GetMapping("/key") - String key() { - return anonymousToken().map(AnonymousAuthenticationToken::getKeyHash).map(String::valueOf).orElse(null); - } - - @GetMapping("/principal") - String principal() { - return anonymousToken().map(AnonymousAuthenticationToken::getName).orElse(null); - } - - Optional anonymousToken() { - return Optional.of(SecurityContextHolder.getContext()) - .map(SecurityContext::getAuthentication) - .filter((a) -> a instanceof AnonymousAuthenticationToken) - .map(AnonymousAuthenticationToken.class::cast); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpBasicTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpBasicTests.java deleted file mode 100644 index 17fa75980ed..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpBasicTests.java +++ /dev/null @@ -1,335 +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.annotation.web.configurers; - -import jakarta.servlet.http.HttpServletRequest; -import org.apache.http.HttpHeaders; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests to verify that all the functionality of <http-basic> attributes is present - * - * @author Rob Winch - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class NamespaceHttpBasicTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - /** - * http/http-basic equivalent - */ - @Test - public void basicAuthenticationWhenUsingDefaultsThenMatchesNamespace() throws Exception { - this.spring.register(HttpBasicConfig.class, UserConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(status().isUnauthorized()); - MockHttpServletRequestBuilder requestWithInvalidPassword = get("/").with(httpBasic("user", "invalid")); - // @formatter:off - this.mvc.perform(requestWithInvalidPassword) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Realm\"")); - // @formatter:on - MockHttpServletRequestBuilder requestWithValidPassword = get("/").with(httpBasic("user", "password")); - this.mvc.perform(requestWithValidPassword).andExpect(status().isNotFound()); - } - - @Test - public void basicAuthenticationWhenUsingDefaultsInLambdaThenMatchesNamespace() throws Exception { - this.spring.register(HttpBasicLambdaConfig.class, UserConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(status().isUnauthorized()); - MockHttpServletRequestBuilder requestWithInvalidPassword = get("/").with(httpBasic("user", "invalid")); - // @formatter:off - this.mvc.perform(requestWithInvalidPassword) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Realm\"")); - // @formatter:on - MockHttpServletRequestBuilder requestWithValidPassword = get("/").with(httpBasic("user", "password")); - this.mvc.perform(requestWithValidPassword).andExpect(status().isNotFound()); - } - - /** - * http@realm equivalent - */ - @Test - public void basicAuthenticationWhenUsingCustomRealmThenMatchesNamespace() throws Exception { - this.spring.register(CustomHttpBasicConfig.class, UserConfig.class).autowire(); - MockHttpServletRequestBuilder requestWithInvalidPassword = get("/").with(httpBasic("user", "invalid")); - // @formatter:off - this.mvc.perform(requestWithInvalidPassword) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Custom Realm\"")); - // @formatter:on - } - - @Test - public void basicAuthenticationWhenUsingCustomRealmInLambdaThenMatchesNamespace() throws Exception { - this.spring.register(CustomHttpBasicLambdaConfig.class, UserConfig.class).autowire(); - MockHttpServletRequestBuilder requestWithInvalidPassword = get("/").with(httpBasic("user", "invalid")); - // @formatter:off - this.mvc.perform(requestWithInvalidPassword) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Custom Realm\"")); - // @formatter:on - } - - /** - * http/http-basic@authentication-details-source-ref equivalent - */ - @Test - public void basicAuthenticationWhenUsingAuthenticationDetailsSourceRefThenMatchesNamespace() throws Exception { - this.spring.register(AuthenticationDetailsSourceHttpBasicConfig.class, UserConfig.class).autowire(); - AuthenticationDetailsSource source = this.spring.getContext() - .getBean(AuthenticationDetailsSource.class); - this.mvc.perform(get("/").with(httpBasic("user", "password"))); - verify(source).buildDetails(any(HttpServletRequest.class)); - } - - @Test - public void basicAuthenticationWhenUsingAuthenticationDetailsSourceRefInLambdaThenMatchesNamespace() - throws Exception { - this.spring.register(AuthenticationDetailsSourceHttpBasicLambdaConfig.class, UserConfig.class).autowire(); - AuthenticationDetailsSource source = this.spring.getContext() - .getBean(AuthenticationDetailsSource.class); - this.mvc.perform(get("/").with(httpBasic("user", "password"))); - verify(source).buildDetails(any(HttpServletRequest.class)); - } - - /** - * http/http-basic@entry-point-ref - */ - @Test - public void basicAuthenticationWhenUsingEntryPointRefThenMatchesNamespace() throws Exception { - this.spring.register(EntryPointRefHttpBasicConfig.class, UserConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(status().is(999)); - this.mvc.perform(get("/").with(httpBasic("user", "invalid"))).andExpect(status().is(999)); - this.mvc.perform(get("/").with(httpBasic("user", "password"))).andExpect(status().isNotFound()); - } - - @Test - public void basicAuthenticationWhenUsingEntryPointRefInLambdaThenMatchesNamespace() throws Exception { - this.spring.register(EntryPointRefHttpBasicLambdaConfig.class, UserConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(status().is(999)); - this.mvc.perform(get("/").with(httpBasic("user", "invalid"))).andExpect(status().is(999)); - this.mvc.perform(get("/").with(httpBasic("user", "password"))).andExpect(status().isNotFound()); - } - - @Configuration - static class UserConfig { - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager( - // @formatter:off - User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build() - // @formatter:on - ); - } - - } - - @Configuration - @EnableWebSecurity - static class HttpBasicConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .httpBasic(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HttpBasicLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().hasRole("USER") - ) - .httpBasic(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class CustomHttpBasicConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .httpBasic((basic) -> basic.realmName("Custom Realm")); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class CustomHttpBasicLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().hasRole("USER") - ) - .httpBasic((httpBasicConfig) -> httpBasicConfig.realmName("Custom Realm")); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class AuthenticationDetailsSourceHttpBasicConfig { - - AuthenticationDetailsSource authenticationDetailsSource = mock( - AuthenticationDetailsSource.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .httpBasic((basic) -> basic - .authenticationDetailsSource(this.authenticationDetailsSource)); - return http.build(); - // @formatter:on - } - - @Bean - AuthenticationDetailsSource authenticationDetailsSource() { - return this.authenticationDetailsSource; - } - - } - - @Configuration - @EnableWebSecurity - static class AuthenticationDetailsSourceHttpBasicLambdaConfig { - - AuthenticationDetailsSource authenticationDetailsSource = mock( - AuthenticationDetailsSource.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .httpBasic((httpBasicConfig) -> httpBasicConfig.authenticationDetailsSource(this.authenticationDetailsSource)); - return http.build(); - // @formatter:on - } - - @Bean - AuthenticationDetailsSource authenticationDetailsSource() { - return this.authenticationDetailsSource; - } - - } - - @Configuration - @EnableWebSecurity - static class EntryPointRefHttpBasicConfig { - - AuthenticationEntryPoint authenticationEntryPoint = (request, response, ex) -> response.setStatus(999); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .httpBasic((basic) -> basic - .authenticationEntryPoint(this.authenticationEntryPoint)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class EntryPointRefHttpBasicLambdaConfig { - - AuthenticationEntryPoint authenticationEntryPoint = (request, response, ex) -> response.setStatus(999); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().hasRole("USER") - ) - .httpBasic((httpBasicConfig) -> httpBasicConfig.authenticationEntryPoint(this.authenticationEntryPoint)); - return http.build(); - // @formatter:on - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpCustomFilterTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpCustomFilterTests.java deleted file mode 100644 index 75fdbc79a71..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpCustomFilterTests.java +++ /dev/null @@ -1,234 +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.annotation.web.configurers; - -import java.io.IOException; -import java.util.List; -import java.util.stream.Collectors; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.assertj.core.api.ListAssert; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.builders.TestHttpSecurities; -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.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.web.filter.OncePerRequestFilter; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * Tests to verify that all the functionality of <custom-filter> attributes is - * present - * - * @author Rob Winch - * @author Josh Cummings - * - */ -@ExtendWith(SpringTestContextExtension.class) -public class NamespaceHttpCustomFilterTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - public void getFiltersWhenFilterAddedBeforeThenBehaviorMatchesNamespace() { - this.spring.register(CustomFilterBeforeConfig.class, UserDetailsServiceConfig.class).autowire(); - assertThatFilters().containsSubsequence(CustomFilter.class, UsernamePasswordAuthenticationFilter.class); - } - - @Test - public void getFiltersWhenFilterAddedAfterThenBehaviorMatchesNamespace() { - this.spring.register(CustomFilterAfterConfig.class, UserDetailsServiceConfig.class).autowire(); - assertThatFilters().containsSubsequence(UsernamePasswordAuthenticationFilter.class, CustomFilter.class); - } - - @Test - public void getFiltersWhenFilterAddedThenBehaviorMatchesNamespace() { - this.spring.register(CustomFilterPositionConfig.class, UserDetailsServiceConfig.class).autowire(); - assertThatFilters().containsExactly(CustomFilter.class); - } - - @Test - public void getFiltersWhenFilterAddedAtPositionThenBehaviorMatchesNamespace() { - this.spring.register(CustomFilterPositionAtConfig.class, UserDetailsServiceConfig.class).autowire(); - assertThatFilters().containsExactly(OtherCustomFilter.class); - } - - @Test - public void getFiltersWhenCustomAuthenticationManagerThenBehaviorMatchesNamespace() { - this.spring.register(NoAuthenticationManagerInHttpConfigurationConfig.class).autowire(); - assertThatFilters().startsWith(CustomFilter.class); - } - - private ListAssert> assertThatFilters() { - FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class); - List> filters = filterChain.getFilters("/") - .stream() - .map(Object::getClass) - .collect(Collectors.toList()); - return assertThat(filters); - } - - @Configuration - @EnableWebSecurity - static class CustomFilterBeforeConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class) - .formLogin(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class CustomFilterAfterConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .addFilterAfter(new CustomFilter(), UsernamePasswordAuthenticationFilter.class) - .formLogin(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class CustomFilterPositionConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - TestHttpSecurities.disableDefaults(http); - http - // this works so long as the CustomFilter extends one of the standard filters - // if not, use addFilterBefore or addFilterAfter - .addFilter(new CustomFilter()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class CustomFilterPositionAtConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - TestHttpSecurities.disableDefaults(http); - http - .addFilterAt(new OtherCustomFilter(), UsernamePasswordAuthenticationFilter.class); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class NoAuthenticationManagerInHttpConfigurationConfig { - - @Bean - AuthenticationManager authenticationManager() { - return new CustomAuthenticationManager(); - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - TestHttpSecurities.disableDefaults(http); - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class); - return http.build(); - // @formatter:on - } - - } - - @Configuration - static class UserDetailsServiceConfig { - - @Bean - UserDetailsService userDetailsService() { - // @formatter:off - UserDetails user = User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build(); - // @formatter:on - return new InMemoryUserDetailsManager(user); - } - - } - - static class CustomFilter extends UsernamePasswordAuthenticationFilter { - - } - - static class OtherCustomFilter extends OncePerRequestFilter { - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException { - filterChain.doFilter(request, response); - } - - } - - static class CustomAuthenticationManager implements AuthenticationManager { - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - return null; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpExpressionHandlerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpExpressionHandlerTests.java deleted file mode 100644 index 0c54e06c821..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpExpressionHandlerTests.java +++ /dev/null @@ -1,138 +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.annotation.web.configurers; - -import java.security.Principal; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.access.expression.DefaultHttpSecurityExpressionHandler; -import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; - -/** - * Tests to verify that all the functionality of <expression-handler> attributes is - * present - * - * @author Rob Winch - * @author Josh Cummings - * - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class NamespaceHttpExpressionHandlerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - @WithMockUser - public void getWhenHasCustomExpressionHandlerThenMatchesNamespace() throws Exception { - this.spring.register(ExpressionHandlerController.class, ExpressionHandlerConfig.class).autowire(); - this.mvc.perform(get("/whoami")).andExpect(content().string("user")); - verifyBean("expressionParser", ExpressionParser.class).parseExpression("hasRole('USER')"); - } - - private T verifyBean(String beanName, Class beanClass) { - return verify(this.spring.getContext().getBean(beanName, beanClass)); - } - - @Configuration - @EnableWebMvc - @EnableWebSecurity - static class ExpressionHandlerConfig { - - @Bean - UserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("rod") - .password("password") - .roles("USER", "ADMIN") - .build(); - return new InMemoryUserDetailsManager(user); - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http, WebExpressionAuthorizationManager.Builder authz) - throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().access(authz.expression("hasRole('USER')")) - ); - // @formatter:on - return http.build(); - } - - @Bean - WebExpressionAuthorizationManager.Builder expressions(DefaultHttpSecurityExpressionHandler expressionHandler) { - return WebExpressionAuthorizationManager.withExpressionHandler(expressionHandler); - } - - @Bean - DefaultHttpSecurityExpressionHandler expressionHandler(ExpressionParser expressionParser) { - DefaultHttpSecurityExpressionHandler expressionHandler = new DefaultHttpSecurityExpressionHandler(); - expressionHandler.setExpressionParser(expressionParser); - return expressionHandler; - } - - @Bean - ExpressionParser expressionParser() { - return spy(new SpelExpressionParser()); - } - - } - - @RestController - private static class ExpressionHandlerController { - - @GetMapping("/whoami") - String whoami(Principal user) { - return user.getName(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFirewallTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFirewallTests.java deleted file mode 100644 index 70b84a61e3c..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFirewallTests.java +++ /dev/null @@ -1,114 +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.annotation.web.configurers; - -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.web.firewall.DefaultHttpFirewall; -import org.springframework.security.web.firewall.FirewalledRequest; -import org.springframework.security.web.firewall.HttpFirewall; -import org.springframework.security.web.firewall.RequestRejectedException; -import org.springframework.test.web.servlet.MockMvc; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests to verify that all the functionality of <http-firewall> attributes is - * present - * - * @author Rob Winch - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class NamespaceHttpFirewallTests { - - public final SpringTestContext rule = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - @Disabled("MockMvc uses UriComponentsBuilder::fromUriString which was changed in https://github.com/spring-projects/spring-framework/issues/32513") - public void requestWhenPathContainsDoubleDotsThenBehaviorMatchesNamespace() throws Exception { - this.rule.register(HttpFirewallConfig.class).autowire(); - this.mvc.perform(get("/public/../private/")).andExpect(status().isBadRequest()); - } - - @Test - public void requestWithCustomFirewallThenBehaviorMatchesNamespace() throws Exception { - this.rule.register(CustomHttpFirewallConfig.class).autowire(); - this.mvc.perform(get("/").param("deny", "true")).andExpect(status().isBadRequest()); - } - - @Test - public void requestWithCustomFirewallBeanThenBehaviorMatchesNamespace() throws Exception { - this.rule.register(CustomHttpFirewallBeanConfig.class).autowire(); - this.mvc.perform(get("/").param("deny", "true")).andExpect(status().isBadRequest()); - } - - @Configuration - @EnableWebSecurity - static class HttpFirewallConfig { - - } - - @Configuration - @EnableWebSecurity - static class CustomHttpFirewallConfig { - - @Bean - WebSecurityCustomizer webSecurityCustomizer() { - return (web) -> web.httpFirewall(new CustomHttpFirewall()); - } - - } - - @Configuration - @EnableWebSecurity - static class CustomHttpFirewallBeanConfig { - - @Bean - HttpFirewall firewall() { - return new CustomHttpFirewall(); - } - - } - - static class CustomHttpFirewall extends DefaultHttpFirewall { - - @Override - public FirewalledRequest getFirewalledRequest(HttpServletRequest request) throws RequestRejectedException { - if (request.getParameter("deny") != null) { - throw new RequestRejectedException("custom rejection"); - } - return super.getFirewalledRequest(request); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFormLoginTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFormLoginTests.java deleted file mode 100644 index d71b29e5bac..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpFormLoginTests.java +++ /dev/null @@ -1,206 +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.annotation.web.configurers; - -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; - -/** - * Tests to verify that all the functionality of <form-login> attributes is present - * - * @author Rob Winch - * @author Josh Cummings - * - */ -@ExtendWith(SpringTestContextExtension.class) -public class NamespaceHttpFormLoginTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void formLoginWhenDefaultConfigurationThenMatchesNamespace() throws Exception { - this.spring.register(FormLoginConfig.class, UserDetailsServiceConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(redirectedUrl("/login")); - this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/login?error")); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .param("username", "user") - .param("password", "password") - .with(csrf()); - // @formatter:on - this.mvc.perform(loginRequest).andExpect(redirectedUrl("/")); - } - - @Test - public void formLoginWithCustomEndpointsThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(FormLoginCustomConfig.class, UserDetailsServiceConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(redirectedUrl("/authentication/login")); - this.mvc.perform(post("/authentication/login/process").with(csrf())) - .andExpect(redirectedUrl("/authentication/login?failed")); - // @formatter:off - MockHttpServletRequestBuilder request = post("/authentication/login/process") - .param("username", "user") - .param("password", "password") - .with(csrf()); - // @formatter:on - this.mvc.perform(request).andExpect(redirectedUrl("/default")); - } - - @Test - public void formLoginWithCustomHandlersThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(FormLoginCustomRefsConfig.class, UserDetailsServiceConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(redirectedUrl("/login")); - this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/custom/failure")); - verifyBean(WebAuthenticationDetailsSource.class).buildDetails(any(HttpServletRequest.class)); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .param("username", "user") - .param("password", "password") - .with(csrf()); - // @formatter:on - this.mvc.perform(loginRequest).andExpect(redirectedUrl("/custom/targetUrl")); - } - - private T verifyBean(Class beanClass) { - return verify(this.spring.getContext().getBean(beanClass)); - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class FormLoginConfig { - - @Bean - WebSecurityCustomizer webSecurityCustomizer() { - return (web) -> web.ignoring().requestMatchers("/resources/**"); - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class FormLoginCustomConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - boolean alwaysUseDefaultSuccess = true; - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin((login) -> login - .usernameParameter("username") // form-login@username-parameter - .passwordParameter("password") // form-login@password-parameter - .loginPage("/authentication/login") // form-login@login-page - .failureUrl("/authentication/login?failed") // form-login@authentication-failure-url - .loginProcessingUrl("/authentication/login/process") // form-login@login-processing-url - .defaultSuccessUrl("/default", alwaysUseDefaultSuccess)); - return http.build(); // form-login@default-target-url / form-login@always-use-default-target - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class FormLoginCustomRefsConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler(); - successHandler.setDefaultTargetUrl("/custom/targetUrl"); - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin((login) -> login - .loginPage("/login") - .failureHandler(new SimpleUrlAuthenticationFailureHandler("/custom/failure")) // form-login@authentication-failure-handler-ref - .successHandler(successHandler) // form-login@authentication-success-handler-ref - .authenticationDetailsSource(authenticationDetailsSource())); - return http.build(); - // @formatter:on - } - - @Bean - WebAuthenticationDetailsSource authenticationDetailsSource() { - return spy(WebAuthenticationDetailsSource.class); - } - - } - - @Configuration - static class UserDetailsServiceConfig { - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager( - // @formatter:off - User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build()); - // @formatter:on - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpHeadersTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpHeadersTests.java deleted file mode 100644 index 5fda749145d..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpHeadersTests.java +++ /dev/null @@ -1,337 +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.annotation.web.configurers; - -import java.net.URI; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.web.SecurityFilterChain; -import org.springframework.security.web.header.writers.StaticHeadersWriter; -import org.springframework.security.web.header.writers.XXssProtectionHeaderWriter; -import org.springframework.security.web.header.writers.frameoptions.StaticAllowFromStrategy; -import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter; -import org.springframework.security.web.util.matcher.AnyRequestMatcher; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.ResultMatcher; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; - -/** - * Tests to verify that all the functionality of <headers> attributes is present - * - * @author Rob Winch - * @author Josh Cummings - * - */ -@ExtendWith(SpringTestContextExtension.class) -public class NamespaceHttpHeadersTests { - - static final Map defaultHeaders = new LinkedHashMap<>(); - static { - defaultHeaders.put("X-Content-Type-Options", "nosniff"); - defaultHeaders.put("X-Frame-Options", "DENY"); - defaultHeaders.put("Strict-Transport-Security", "max-age=31536000 ; includeSubDomains"); - defaultHeaders.put("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate"); - defaultHeaders.put("Expires", "0"); - defaultHeaders.put("Pragma", "no-cache"); - defaultHeaders.put("X-XSS-Protection", "0"); - } - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void secureRequestWhenDefaultConfigThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(HeadersDefaultConfig.class).autowire(); - this.mvc.perform(get("/").secure(true)).andExpect(includesDefaults()); - } - - @Test - public void secureRequestWhenCacheControlOnlyThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(HeadersCacheControlConfig.class).autowire(); - this.mvc.perform(get("/").secure(true)).andExpect(includes("Cache-Control", "Expires", "Pragma")); - } - - @Test - public void secureRequestWhenHstsOnlyThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(HstsConfig.class).autowire(); - this.mvc.perform(get("/").secure(true)).andExpect(includes("Strict-Transport-Security")); - } - - @Test - public void requestWhenHstsCustomThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(HstsCustomConfig.class).autowire(); - this.mvc.perform(get("/")) - .andExpect(includes(Collections.singletonMap("Strict-Transport-Security", "max-age=15768000"))); - } - - @Test - public void requestWhenFrameOptionsSameOriginThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(FrameOptionsSameOriginConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(includes(Collections.singletonMap("X-Frame-Options", "SAMEORIGIN"))); - } - - @Test - public void requestWhenFrameOptionsAllowFromThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(FrameOptionsAllowFromConfig.class).autowire(); - this.mvc.perform(get("/")) - .andExpect(includes(Collections.singletonMap("X-Frame-Options", "ALLOW-FROM https://example.com"))); - } - - @Test - public void requestWhenXssOnlyThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(XssProtectionConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(includes("X-XSS-Protection")); - } - - @Test - public void requestWhenXssCustomThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(XssProtectionCustomConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(includes(Collections.singletonMap("X-XSS-Protection", "1; mode=block"))); - } - - @Test - public void requestWhenXContentTypeOptionsOnlyThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(ContentTypeOptionsConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(includes("X-Content-Type-Options")); - } - - @Test - public void requestWhenCustomHeaderOnlyThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(HeaderRefConfig.class).autowire(); - this.mvc.perform(get("/")) - .andExpect(includes(Collections.singletonMap("customHeaderName", "customHeaderValue"))); - } - - private static ResultMatcher includesDefaults() { - return includes(defaultHeaders); - } - - private static ResultMatcher includes(String... headerNames) { - return includes(defaultHeaders, headerNames); - } - - private static ResultMatcher includes(Map headers) { - return includes(headers, headers.keySet().toArray(new String[headers.size()])); - } - - private static ResultMatcher includes(Map headers, String... headerNames) { - return (result) -> { - assertThat(result.getResponse().getHeaderNames()).hasSameSizeAs(headerNames); - for (String headerName : headerNames) { - header().string(headerName, headers.get(headerName)).match(result); - } - }; - } - - @Configuration - @EnableWebSecurity - static class HeadersDefaultConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HeadersCacheControlConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .cacheControl(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HstsConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .httpStrictTransportSecurity(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HstsCustomConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - // hsts@request-matcher-ref, hsts@max-age-seconds, hsts@include-subdomains - .defaultsDisabled() - .httpStrictTransportSecurity((hsts) -> hsts - .requestMatcher(AnyRequestMatcher.INSTANCE) - .maxAgeInSeconds(15768000) - .includeSubDomains(false))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class FrameOptionsSameOriginConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - // frame-options@policy=SAMEORIGIN - .defaultsDisabled() - .frameOptions((frameOptions) -> frameOptions.sameOrigin())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class FrameOptionsAllowFromConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - // frame-options@ref - .defaultsDisabled() - .addHeaderWriter(new XFrameOptionsHeaderWriter( - new StaticAllowFromStrategy(URI.create("https://example.com"))))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class XssProtectionConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - // xss-protection - .defaultsDisabled() - .xssProtection(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class XssProtectionCustomConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - // xss-protection@enabled and xss-protection@block - .defaultsDisabled() - .xssProtection((xss) -> xss - .headerValue(XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK))); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - static class ContentTypeOptionsConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - // content-type-options - .defaultsDisabled() - .contentTypeOptions(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HeaderRefConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .headers((headers) -> headers - .defaultsDisabled() - .addHeaderWriter(new StaticHeadersWriter("customHeaderName", "customHeaderValue"))); - return http.build(); - // @formatter:on - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpInterceptUrlTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpInterceptUrlTests.java deleted file mode 100644 index 697ee1f91cb..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpInterceptUrlTests.java +++ /dev/null @@ -1,181 +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.annotation.web.configurers; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests to verify that all the functionality of <intercept-url> attributes is - * present - * - * @author Rob Winch - * @author Josh Cummings - * - */ -@ExtendWith(SpringTestContextExtension.class) -public class NamespaceHttpInterceptUrlTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void unauthenticatedRequestWhenUrlRequiresAuthenticationThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(HttpInterceptUrlConfig.class).autowire(); - this.mvc.perform(get("/users")).andExpect(status().isForbidden()); - } - - @Test - public void authenticatedRequestWhenUrlRequiresElevatedPrivilegesThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(HttpInterceptUrlConfig.class).autowire(); - MockHttpServletRequestBuilder requestWithUser = get("/users").with(authentication(user("ROLE_USER"))); - this.mvc.perform(requestWithUser).andExpect(status().isForbidden()); - } - - @Test - public void authenticatedRequestWhenAuthorizedThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(HttpInterceptUrlConfig.class, BaseController.class).autowire(); - MockHttpServletRequestBuilder requestWithAdmin = get("/users").with(authentication(user("ROLE_ADMIN"))); - this.mvc.perform(requestWithAdmin).andExpect(status().isOk()).andReturn(); - } - - @Test - @Disabled // see https://github.com/spring-projects/spring-security/issues/17747 - public void requestWhenMappedByPostInterceptUrlThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(HttpInterceptUrlConfig.class, BaseController.class).autowire(); - MockHttpServletRequestBuilder getWithUser = get("/admin/post").with(authentication(user("ROLE_USER"))); - this.mvc.perform(getWithUser).andExpect(status().isOk()); - MockHttpServletRequestBuilder postWithUser = post("/admin/post").with(authentication(user("ROLE_USER"))); - this.mvc.perform(postWithUser).andExpect(status().isForbidden()); - MockHttpServletRequestBuilder requestWithAdmin = post("/admin/post").with(csrf()) - .with(authentication(user("ROLE_ADMIN"))); - this.mvc.perform(requestWithAdmin).andExpect(status().isOk()); - } - - @Test - public void requestWhenRequiresChannelThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(HttpInterceptUrlConfig.class).autowire(); - this.mvc.perform(get("/login")).andExpect(redirectedUrl("https://localhost/login")); - this.mvc.perform(get("/secured/a")).andExpect(redirectedUrl("https://localhost/secured/a")); - this.mvc.perform(get("https://localhost/user")).andExpect(redirectedUrl("http://localhost/user")); - } - - private static Authentication user(String role) { - return UsernamePasswordAuthenticationToken.authenticated("user", null, - AuthorityUtils.createAuthorityList(role)); - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class HttpInterceptUrlConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests.requestMatchers( - // the line below is similar to intercept-url@pattern: - // - //" access="hasRole('ROLE_ADMIN')"/> -"/users**", "/sessions/**").hasRole("ADMIN").requestMatchers( - // the line below is similar to intercept-url@method: - // - //" access="hasRole('ROLE_ADMIN')" method="POST"/> -HttpMethod.POST, "/admin/post", "/admin/another-post/**").hasRole("ADMIN") - .requestMatchers("/signup").permitAll() - .anyRequest().hasRole("USER")) - .requiresChannel((channel) -> channel.requestMatchers("/login", "/secured/**") - // NOTE: channel security is configured separately of authorization (i.e. intercept-url@access - // the line below is similar to intercept-url@requires-channel="https": - // - //" requires-channel="https"/> - .requiresSecure().anyRequest().requiresInsecure()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin()); - } - - } - - @RestController - static class BaseController { - - @GetMapping("/users") - String users() { - return "ok"; - } - - @GetMapping("/sessions") - String sessions() { - return "sessions"; - } - - @RequestMapping("/admin/post") - String adminPost() { - return "adminPost"; - } - - @GetMapping("/admin/another-post") - String adminAnotherPost() { - return "adminAnotherPost"; - } - - @GetMapping("/signup") - String signup() { - return "signup"; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpJeeTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpJeeTests.java deleted file mode 100644 index c09cdbdfe62..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpJeeTests.java +++ /dev/null @@ -1,159 +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.annotation.web.configurers; - -import java.security.Principal; -import java.util.stream.Collectors; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.userdetails.AuthenticationUserDetailsService; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests to verify that all the functionality of <jee> attributes is present - * - * @author Rob Winch - * @author Josh Cummings - * - */ -@ExtendWith(SpringTestContextExtension.class) -public class NamespaceHttpJeeTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void requestWhenJeeUserThenBehaviorDiffersFromNamespaceForRoleNames() throws Exception { - this.spring.register(JeeMappableRolesConfig.class, BaseController.class).autowire(); - Principal user = mock(Principal.class); - given(user.getName()).willReturn("joe"); - this.mvc.perform(get("/roles").principal(user).with((request) -> { - request.addUserRole("ROLE_admin"); - request.addUserRole("ROLE_user"); - request.addUserRole("ROLE_unmapped"); - return request; - })).andExpect(status().isOk()).andExpect(content().string("ROLE_admin,ROLE_user")); - } - - @Test - public void requestWhenCustomAuthenticatedUserDetailsServiceThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(JeeUserServiceRefConfig.class, BaseController.class).autowire(); - Principal user = mock(Principal.class); - given(user.getName()).willReturn("joe"); - User result = new User(user.getName(), "N/A", true, true, true, true, - AuthorityUtils.createAuthorityList("ROLE_user")); - given(bean(AuthenticationUserDetailsService.class).loadUserDetails(any())).willReturn(result); - this.mvc.perform(get("/roles").principal(user)) - .andExpect(status().isOk()) - .andExpect(content().string("ROLE_user")); - verifyBean(AuthenticationUserDetailsService.class).loadUserDetails(any()); - } - - private T bean(Class beanClass) { - return this.spring.getContext().getBean(beanClass); - } - - private T verifyBean(Class beanClass) { - return verify(this.spring.getContext().getBean(beanClass)); - } - - @Configuration - @EnableWebSecurity - public static class JeeMappableRolesConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("user")) - .jee((jee) -> jee - .mappableRoles("user", "admin")); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - public static class JeeUserServiceRefConfig { - - private final AuthenticationUserDetailsService authenticationUserDetailsService = mock( - AuthenticationUserDetailsService.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("user")) - .jee((jee) -> jee - .mappableAuthorities("ROLE_user", "ROLE_admin") - .authenticatedUserDetailsService(this.authenticationUserDetailsService)); - return http.build(); - // @formatter:on - } - - @Bean - public AuthenticationUserDetailsService authenticationUserDetailsService() { - return this.authenticationUserDetailsService; - } - - } - - @RestController - static class BaseController { - - @GetMapping("/authenticated") - String authenticated(Authentication authentication) { - return authentication.getName(); - } - - @GetMapping("/roles") - String roles(Authentication authentication) { - return authentication.getAuthorities().stream().map(Object::toString).collect(Collectors.joining(",")); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpLogoutTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpLogoutTests.java deleted file mode 100644 index e9ca3ff1622..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpLogoutTests.java +++ /dev/null @@ -1,264 +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.annotation.web.configurers; - -import java.util.Objects; -import java.util.Optional; -import java.util.function.Predicate; - -import jakarta.servlet.http.HttpSession; -import org.assertj.core.api.Condition; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.ResultMatcher; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests to verify that all the functionality of <logout> attributes is present - * - * @author Rob Winch - * @author Josh Cummings - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class NamespaceHttpLogoutTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - /** - * http/logout equivalent - */ - @Test - @WithMockUser - public void logoutWhenUsingDefaultsThenMatchesNamespace() throws Exception { - this.spring.register(HttpLogoutConfig.class).autowire(); - // @formatter:off - this.mvc.perform(post("/logout").with(csrf())) - .andExpect(authenticated(false)) - .andExpect(redirectedUrl("/login?logout")) - .andExpect(noCookies()) - .andExpect(session(Objects::isNull)); - // @formatter:on - } - - @Test - @WithMockUser - public void logoutWhenDisabledInLambdaThenRespondsWithNotFound() throws Exception { - this.spring.register(HttpLogoutDisabledInLambdaConfig.class).autowire(); - MockHttpServletRequestBuilder logoutRequest = post("/logout").with(csrf()).with(user("user")); - this.mvc.perform(logoutRequest).andExpect(status().isNotFound()); - } - - /** - * http/logout custom - */ - @Test - @WithMockUser - public void logoutWhenUsingVariousCustomizationsMatchesNamespace() throws Exception { - this.spring.register(CustomHttpLogoutConfig.class).autowire(); - // @formatter:off - this.mvc.perform(post("/custom-logout").with(csrf())) - .andExpect(authenticated(false)) - .andExpect(redirectedUrl("/logout-success")) - .andExpect((result) -> assertThat(result.getResponse().getCookies()).hasSize(1)) - .andExpect(cookie().maxAge("remove", 0)) - .andExpect(session(Objects::nonNull)); - // @formatter:on - } - - @Test - @WithMockUser - public void logoutWhenUsingVariousCustomizationsInLambdaThenMatchesNamespace() throws Exception { - this.spring.register(CustomHttpLogoutInLambdaConfig.class).autowire(); - // @formatter:off - this.mvc.perform(post("/custom-logout").with(csrf())) - .andExpect(authenticated(false)) - .andExpect(redirectedUrl("/logout-success")) - .andExpect((result) -> assertThat(result.getResponse().getCookies()).hasSize(1)) - .andExpect(cookie().maxAge("remove", 0)) - .andExpect(session(Objects::nonNull)); - // @formatter:on - } - - /** - * http/logout@success-handler-ref - */ - @Test - @WithMockUser - public void logoutWhenUsingSuccessHandlerRefThenMatchesNamespace() throws Exception { - this.spring.register(SuccessHandlerRefHttpLogoutConfig.class).autowire(); - // @formatter:off - this.mvc.perform(post("/logout").with(csrf())) - .andExpect(authenticated(false)) - .andExpect(redirectedUrl("/SuccessHandlerRefHttpLogoutConfig")) - .andExpect(noCookies()) - .andExpect(session(Objects::isNull)); - // @formatter:on - } - - @Test - @WithMockUser - public void logoutWhenUsingSuccessHandlerRefInLambdaThenMatchesNamespace() throws Exception { - this.spring.register(SuccessHandlerRefHttpLogoutInLambdaConfig.class).autowire(); - // @formatter:off - this.mvc.perform(post("/logout").with(csrf())) - .andExpect(authenticated(false)) - .andExpect(redirectedUrl("/SuccessHandlerRefHttpLogoutConfig")) - .andExpect(noCookies()) - .andExpect(session(Objects::isNull)); - // @formatter:on - } - - ResultMatcher authenticated(boolean authenticated) { - return (result) -> assertThat(Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication()) - .map(Authentication::isAuthenticated) - .orElse(false)).isEqualTo(authenticated); - } - - ResultMatcher noCookies() { - return (result) -> assertThat(result.getResponse().getCookies()).isEmpty(); - } - - ResultMatcher session(Predicate sessionPredicate) { - return (result) -> assertThat(result.getRequest().getSession(false)) - .is(new Condition<>(sessionPredicate, "sessionPredicate failed")); - } - - @Configuration - @EnableWebSecurity - static class HttpLogoutConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - static class HttpLogoutDisabledInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.logout(AbstractHttpConfigurer::disable); - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - static class CustomHttpLogoutConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .logout((logout) -> logout - .deleteCookies("remove") // logout@delete-cookies - .invalidateHttpSession(false) // logout@invalidate-session=false (default is true) - .logoutUrl("/custom-logout") // logout@logout-url (default is /logout) - .logoutSuccessUrl("/logout-success")); - return http.build(); // logout@success-url (default is /login?logout) - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class CustomHttpLogoutInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .logout((logout) -> logout.deleteCookies("remove") - .invalidateHttpSession(false) - .logoutUrl("/custom-logout") - .logoutSuccessUrl("/logout-success") - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class SuccessHandlerRefHttpLogoutConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler(); - logoutSuccessHandler.setDefaultTargetUrl("/SuccessHandlerRefHttpLogoutConfig"); - // @formatter:off - http - .logout((logout) -> logout - .logoutSuccessHandler(logoutSuccessHandler)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class SuccessHandlerRefHttpLogoutInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - SimpleUrlLogoutSuccessHandler logoutSuccessHandler = new SimpleUrlLogoutSuccessHandler(); - logoutSuccessHandler.setDefaultTargetUrl("/SuccessHandlerRefHttpLogoutConfig"); - // @formatter:off - http - .logout((logout) -> logout.logoutSuccessHandler(logoutSuccessHandler)); - return http.build(); - // @formatter:on - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpPortMappingsTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpPortMappingsTests.java deleted file mode 100644 index 8d883016676..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpPortMappingsTests.java +++ /dev/null @@ -1,91 +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.annotation.web.configurers; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; - -/** - * Tests to verify that all the functionality of <port-mappings> attributes is - * present - * - * @author Rob Winch - * @author Josh Cummings - * - */ -@ExtendWith(SpringTestContextExtension.class) -public class NamespaceHttpPortMappingsTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void portMappingWhenRequestRequiresChannelThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(HttpInterceptUrlWithPortMapperConfig.class).autowire(); - this.mvc.perform(get("http://localhost:9080/login")).andExpect(redirectedUrl("https://localhost:9443/login")); - this.mvc.perform(get("http://localhost:9080/secured/a")) - .andExpect(redirectedUrl("https://localhost:9443/secured/a")); - this.mvc.perform(get("https://localhost:9443/user")).andExpect(redirectedUrl("http://localhost:9080/user")); - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class HttpInterceptUrlWithPortMapperConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .portMapper((mapper) -> mapper - .http(9080).mapsTo(9443)) - .requiresChannel((channel) -> channel - .requestMatchers("/login", "/secured/**").requiresSecure() - .anyRequest().requiresInsecure()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin()); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpRequestCacheTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpRequestCacheTests.java deleted file mode 100644 index 371b5018aad..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpRequestCacheTests.java +++ /dev/null @@ -1,132 +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.annotation.web.configurers; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.savedrequest.RequestCache; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests to verify that all the functionality of <request-cache> attributes is - * present - * - * @author Rob Winch - * @author Josh Cummings - * - */ -@ExtendWith(SpringTestContextExtension.class) -public class NamespaceHttpRequestCacheTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void requestWhenCustomRequestCacheThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(RequestCacheRefConfig.class).autowire(); - this.mvc.perform(get("/")).andExpect(status().isForbidden()); - verifyBean(RequestCache.class).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void requestWhenDefaultConfigurationThenUsesHttpSessionRequestCache() throws Exception { - this.spring.register(DefaultRequestCacheRefConfig.class).autowire(); - MvcResult result = this.mvc.perform(get("/")).andExpect(status().isForbidden()).andReturn(); - HttpSession session = result.getRequest().getSession(false); - assertThat(session).isNotNull(); - assertThat(session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")).isNotNull(); - } - - private T verifyBean(Class beanClass) { - return verify(this.spring.getContext().getBean(beanClass)); - } - - @Configuration - @EnableWebSecurity - static class RequestCacheRefConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .requestCache((cache) -> cache - .requestCache(requestCache())); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin()); - } - - @Bean - RequestCache requestCache() { - return mock(RequestCache.class); - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultRequestCacheRefConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin()); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpServerAccessDeniedHandlerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpServerAccessDeniedHandlerTests.java deleted file mode 100644 index fc4a6df1e6e..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpServerAccessDeniedHandlerTests.java +++ /dev/null @@ -1,193 +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.annotation.web.configurers; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.test.web.servlet.MockMvc; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests to verify that all the functionality of <access-denied-handler> attributes - * is present - * - * @author Rob Winch - * @author Josh Cummings - * - */ -@ExtendWith(SpringTestContextExtension.class) -public class NamespaceHttpServerAccessDeniedHandlerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void requestWhenCustomAccessDeniedPageThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(AccessDeniedPageConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("/").with(authentication(user()))) - .andExpect(status().isForbidden()) - .andExpect(forwardedUrl("/AccessDeniedPageConfig")); - // @formatter:on - } - - @Test - public void requestWhenCustomAccessDeniedPageInLambdaThenForwardedToCustomPage() throws Exception { - this.spring.register(AccessDeniedPageInLambdaConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("/").with(authentication(user()))) - .andExpect(status().isForbidden()) - .andExpect(forwardedUrl("/AccessDeniedPageConfig")); - // @formatter:on - } - - @Test - public void requestWhenCustomAccessDeniedHandlerThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(AccessDeniedHandlerRefConfig.class).autowire(); - this.mvc.perform(get("/").with(authentication(user()))); - verifyBean(AccessDeniedHandler.class).handle(any(HttpServletRequest.class), any(HttpServletResponse.class), - any(AccessDeniedException.class)); - } - - @Test - public void requestWhenCustomAccessDeniedHandlerInLambdaThenBehaviorMatchesNamespace() throws Exception { - this.spring.register(AccessDeniedHandlerRefInLambdaConfig.class).autowire(); - this.mvc.perform(get("/").with(authentication(user()))); - verify(AccessDeniedHandlerRefInLambdaConfig.accessDeniedHandler).handle(any(HttpServletRequest.class), - any(HttpServletResponse.class), any(AccessDeniedException.class)); - } - - private static Authentication user() { - return UsernamePasswordAuthenticationToken.authenticated("user", null, AuthorityUtils.NO_AUTHORITIES); - } - - private T verifyBean(Class beanClass) { - return verify(this.spring.getContext().getBean(beanClass)); - } - - @Configuration - @EnableWebSecurity - static class AccessDeniedPageConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().denyAll()) - .exceptionHandling((handling) -> handling - .accessDeniedPage("/AccessDeniedPageConfig")); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class AccessDeniedPageInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().denyAll() - ) - .exceptionHandling((exceptionHandling) -> exceptionHandling.accessDeniedPage("/AccessDeniedPageConfig") - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class AccessDeniedHandlerRefConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().denyAll()) - .exceptionHandling((handling) -> handling - .accessDeniedHandler(accessDeniedHandler())); - return http.build(); - // @formatter:on - } - - @Bean - AccessDeniedHandler accessDeniedHandler() { - return mock(AccessDeniedHandler.class); - } - - } - - @Configuration - @EnableWebSecurity - static class AccessDeniedHandlerRefInLambdaConfig { - - static AccessDeniedHandler accessDeniedHandler = mock(AccessDeniedHandler.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().denyAll() - ) - .exceptionHandling((exceptionHandling) -> exceptionHandling.accessDeniedHandler(accessDeniedHandler()) - ); - return http.build(); - // @formatter:on - } - - @Bean - AccessDeniedHandler accessDeniedHandler() { - return accessDeniedHandler; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpX509Tests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpX509Tests.java deleted file mode 100644 index 5538cc2ebc3..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpX509Tests.java +++ /dev/null @@ -1,331 +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.annotation.web.configurers; - -import java.io.InputStream; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; - -import javax.security.auth.x500.X500Principal; - -import jakarta.servlet.http.HttpServletRequest; -import org.bouncycastle.asn1.x500.X500Name; -import org.bouncycastle.asn1.x500.style.BCStyle; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ClassPathResource; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; - -/** - * Tests to verify that all the functionality of <x509> attributes is present in - * Java config - * - * @author Rob Winch - * @author Josh Cummings - * - */ -@ExtendWith(SpringTestContextExtension.class) -public class NamespaceHttpX509Tests { - - private static final User USER = new User("customuser", "password", - AuthorityUtils.createAuthorityList("ROLE_USER")); - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void x509AuthenticationWhenUsingX509DefaultConfigurationThenMatchesNamespace() throws Exception { - this.spring.register(X509Config.class, X509Controller.class).autowire(); - X509Certificate certificate = loadCert("rod.cer"); - this.mvc.perform(get("/whoami").with(x509(certificate))).andExpect(content().string("rod")); - } - - @Test - public void x509AuthenticationWhenHasCustomAuthenticationDetailsSourceThenMatchesNamespace() throws Exception { - this.spring.register(AuthenticationDetailsSourceRefConfig.class, X509Controller.class).autowire(); - X509Certificate certificate = loadCert("rod.cer"); - this.mvc.perform(get("/whoami").with(x509(certificate))).andExpect(content().string("rod")); - verifyBean(AuthenticationDetailsSource.class).buildDetails(any()); - } - - @Test - public void x509AuthenticationWhenHasSubjectPrincipalRegexThenMatchesNamespace() throws Exception { - this.spring.register(SubjectPrincipalRegexConfig.class, X509Controller.class).autowire(); - X509Certificate certificate = loadCert("rodatexampledotcom.cer"); - this.mvc.perform(get("/whoami").with(x509(certificate))).andExpect(content().string("rod")); - } - - @Test - public void x509AuthenticationWhenHasCustomPrincipalExtractorThenMatchesNamespace() throws Exception { - this.spring.register(CustomPrincipalExtractorConfig.class, X509Controller.class).autowire(); - X509Certificate certificate = loadCert("rodatexampledotcom.cer"); - this.mvc.perform(get("/whoami").with(x509(certificate))).andExpect(content().string("rod@example.com")); - } - - @Test - public void x509AuthenticationWhenHasCustomUserDetailsServiceThenMatchesNamespace() throws Exception { - this.spring.register(UserDetailsServiceRefConfig.class, X509Controller.class).autowire(); - X509Certificate certificate = loadCert("rodatexampledotcom.cer"); - this.mvc.perform(get("/whoami").with(x509(certificate))).andExpect(content().string("customuser")); - } - - @Test - public void x509AuthenticationWhenHasCustomAuthenticationUserDetailsServiceThenMatchesNamespace() throws Exception { - this.spring.register(AuthenticationUserDetailsServiceConfig.class, X509Controller.class).autowire(); - X509Certificate certificate = loadCert("rodatexampledotcom.cer"); - this.mvc.perform(get("/whoami").with(x509(certificate))).andExpect(content().string("customuser")); - } - - T loadCert(String location) { - try (InputStream is = new ClassPathResource(location).getInputStream()) { - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - return (T) certFactory.generateCertificate(is); - } - catch (Exception ex) { - throw new IllegalArgumentException(ex); - } - } - - T verifyBean(Class beanClass) { - return verify(this.spring.getContext().getBean(beanClass)); - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - public static class X509Config { - - @Bean - UserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("rod") - .password("password") - .roles("USER", "ADMIN") - .build(); - return new InMemoryUserDetailsManager(user); - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .x509(withDefaults()); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class AuthenticationDetailsSourceRefConfig { - - @Bean - UserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("rod") - .password("password") - .roles("USER", "ADMIN") - .build(); - return new InMemoryUserDetailsManager(user); - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .x509((x509) -> x509 - .authenticationDetailsSource(authenticationDetailsSource())); - // @formatter:on - return http.build(); - } - - @Bean - AuthenticationDetailsSource authenticationDetailsSource() { - return mock(AuthenticationDetailsSource.class); - } - - } - - @EnableWebMvc - @Configuration - @EnableWebSecurity - public static class SubjectPrincipalRegexConfig { - - @Bean - UserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("rod") - .password("password") - .roles("USER", "ADMIN") - .build(); - return new InMemoryUserDetailsManager(user); - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .x509((x509) -> x509 - .subjectPrincipalRegex("CN=(.*?)@example.com(?:,|$)")); - // @formatter:on - return http.build(); - } - - } - - @EnableWebMvc - @Configuration - @EnableWebSecurity - public static class CustomPrincipalExtractorConfig { - - @Bean - UserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("rod@example.com") - .password("password") - .roles("USER", "ADMIN") - .build(); - return new InMemoryUserDetailsManager(user); - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .x509((x509) -> x509 - .x509PrincipalExtractor(this::extractCommonName)); - // @formatter:on - return http.build(); - } - - private String extractCommonName(X509Certificate certificate) { - X500Principal principal = certificate.getSubjectX500Principal(); - return new X500Name(principal.getName()).getRDNs(BCStyle.CN)[0].getFirst().getValue().toString(); - } - - } - - @EnableWebMvc - @Configuration - @EnableWebSecurity - public static class UserDetailsServiceRefConfig { - - @Bean - UserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("rod") - .password("password") - .roles("USER", "ADMIN") - .build(); - return new InMemoryUserDetailsManager(user); - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .x509((x509) -> x509 - .userDetailsService((username) -> USER)); - // @formatter:on - return http.build(); - } - - } - - @EnableWebMvc - @Configuration - @EnableWebSecurity - public static class AuthenticationUserDetailsServiceConfig { - - @Bean - UserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("rod") - .password("password") - .roles("USER", "ADMIN") - .build(); - return new InMemoryUserDetailsManager(user); - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .x509((x509) -> x509 - .authenticationUserDetailsService((authentication) -> USER)); - // @formatter:on - return http.build(); - } - - } - - @RestController - public static class X509Controller { - - @GetMapping("/whoami") - public String whoami(@AuthenticationPrincipal(expression = "username") String name) { - return name; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.java deleted file mode 100644 index 7be22947be7..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.java +++ /dev/null @@ -1,535 +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.annotation.web.configurers; - -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.annotation.Order; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.authentication.RememberMeAuthenticationToken; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.RememberMeServices; -import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices; -import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken; -import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.test.web.servlet.request.RequestPostProcessor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests to verify that all the functionality of <anonymous> attributes is present - * - * @author Rob Winch - * @author Josh Cummings - * - */ -@ExtendWith(SpringTestContextExtension.class) -public class NamespaceRememberMeTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void rememberMeLoginWhenUsingDefaultsThenMatchesNamespace() throws Exception { - this.spring.register(RememberMeConfig.class, SecurityController.class).autowire(); - MvcResult result = this.mvc.perform(post("/login").with(rememberMeLogin())).andReturn(); - MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); - Cookie rememberMe = result.getResponse().getCookie("remember-me"); - assertThat(rememberMe).isNotNull(); - this.mvc.perform(get("/authentication-class").cookie(rememberMe)) - .andExpect(content().string(RememberMeAuthenticationToken.class.getName())); - // @formatter:off - MockHttpServletRequestBuilder logoutRequest = post("/logout") - .with(csrf()) - .session(session) - .cookie(rememberMe); - result = this.mvc.perform(logoutRequest) - .andExpect(redirectedUrl("/login?logout")) - .andReturn(); - // @formatter:on - rememberMe = result.getResponse().getCookie("remember-me"); - assertThat(rememberMe).isNotNull().extracting(Cookie::getMaxAge).isEqualTo(0); - // @formatter:off - MockHttpServletRequestBuilder authenticationClassRequest = post("/authentication-class") - .with(csrf()) - .cookie(rememberMe); - this.mvc.perform(authenticationClassRequest) - .andExpect(redirectedUrl("/login")) - .andReturn(); - // @formatter:on - } - - // SEC-3170 - RememberMeService implementations should not have to also implement - // LogoutHandler - @Test - public void logoutWhenCustomRememberMeServicesDeclaredThenUses() throws Exception { - RememberMeServicesRefConfig.REMEMBER_ME_SERVICES = mock(RememberMeServicesWithoutLogoutHandler.class); - this.spring.register(RememberMeServicesRefConfig.class).autowire(); - this.mvc.perform(get("/")); - verify(RememberMeServicesRefConfig.REMEMBER_ME_SERVICES).autoLogin(any(HttpServletRequest.class), - any(HttpServletResponse.class)); - this.mvc.perform(post("/login").with(csrf())); - verify(RememberMeServicesRefConfig.REMEMBER_ME_SERVICES).loginFail(any(HttpServletRequest.class), - any(HttpServletResponse.class)); - } - - @Test - public void rememberMeLoginWhenAuthenticationSuccessHandlerDeclaredThenUses() throws Exception { - AuthSuccessConfig.SUCCESS_HANDLER = mock(AuthenticationSuccessHandler.class); - this.spring.register(AuthSuccessConfig.class).autowire(); - MvcResult result = this.mvc.perform(post("/login").with(rememberMeLogin())).andReturn(); - verifyNoMoreInteractions(AuthSuccessConfig.SUCCESS_HANDLER); - Cookie rememberMe = result.getResponse().getCookie("remember-me"); - assertThat(rememberMe).isNotNull(); - this.mvc.perform(get("/somewhere").cookie(rememberMe)); - verify(AuthSuccessConfig.SUCCESS_HANDLER).onAuthenticationSuccess(any(HttpServletRequest.class), - any(HttpServletResponse.class), any(Authentication.class)); - } - - @Test - public void rememberMeLoginWhenKeyDeclaredThenMatchesNamespace() throws Exception { - this.spring.register(WithoutKeyConfig.class, SecurityController.class).autowire(); - MockHttpServletRequestBuilder requestWithRememberme = post("/without-key/login").with(rememberMeLogin()); - // @formatter:off - Cookie withoutKey = this.mvc.perform(requestWithRememberme) - .andReturn() - .getResponse() - .getCookie("remember-me"); - // @formatter:on - MockHttpServletRequestBuilder somewhereRequest = get("/somewhere").cookie(withoutKey); - // @formatter:off - this.mvc.perform(somewhereRequest) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); - MockHttpServletRequestBuilder loginWithRememberme = post("/login").with(rememberMeLogin()); - Cookie withKey = this.mvc.perform(loginWithRememberme) - .andReturn() - .getResponse() - .getCookie("remember-me"); - this.mvc.perform(get("/somewhere").cookie(withKey)) - .andExpect(status().isNotFound()); - // @formatter:on - } - - // http/remember-me@services-alias is not supported use standard aliasing instead - // (i.e. @Bean("alias")) - // http/remember-me@data-source-ref is not supported directly. Instead use - // http/remember-me@token-repository-ref example - @Test - public void rememberMeLoginWhenDeclaredTokenRepositoryThenMatchesNamespace() throws Exception { - TokenRepositoryRefConfig.TOKEN_REPOSITORY = mock(PersistentTokenRepository.class); - this.spring.register(TokenRepositoryRefConfig.class).autowire(); - this.mvc.perform(post("/login").with(rememberMeLogin())); - verify(TokenRepositoryRefConfig.TOKEN_REPOSITORY).createNewToken(any(PersistentRememberMeToken.class)); - } - - @Test - public void rememberMeLoginWhenTokenValidityDeclaredThenMatchesNamespace() throws Exception { - this.spring.register(TokenValiditySecondsConfig.class).autowire(); - // @formatter:off - Cookie expiredRememberMe = this.mvc.perform(post("/login").with(rememberMeLogin())) - .andReturn() - .getResponse() - .getCookie("remember-me"); - // @formatter:on - assertThat(expiredRememberMe).extracting(Cookie::getMaxAge).isEqualTo(314); - } - - @Test - public void rememberMeLoginWhenUsingDefaultsThenCookieMaxAgeMatchesNamespace() throws Exception { - this.spring.register(RememberMeConfig.class).autowire(); - // @formatter:off - Cookie expiredRememberMe = this.mvc.perform(post("/login").with(rememberMeLogin())) - .andReturn() - .getResponse() - .getCookie("remember-me"); - // @formatter:on - assertThat(expiredRememberMe).extracting(Cookie::getMaxAge).isEqualTo(AbstractRememberMeServices.TWO_WEEKS_S); - } - - @Test - public void rememberMeLoginWhenUsingSecureCookieThenMatchesNamespace() throws Exception { - this.spring.register(UseSecureCookieConfig.class).autowire(); - // @formatter:off - Cookie secureCookie = this.mvc.perform(post("/login").with(rememberMeLogin())) - .andReturn() - .getResponse() - .getCookie("remember-me"); - // @formatter:on - assertThat(secureCookie).extracting(Cookie::getSecure).isEqualTo(true); - } - - @Test - public void rememberMeLoginWhenUsingDefaultsThenCookieSecurityMatchesNamespace() throws Exception { - this.spring.register(RememberMeConfig.class).autowire(); - // @formatter:off - Cookie secureCookie = this.mvc.perform(post("/login").with(rememberMeLogin()).secure(true)) - .andReturn() - .getResponse() - .getCookie("remember-me"); - // @formatter:on - assertThat(secureCookie).extracting(Cookie::getSecure).isEqualTo(true); - } - - @Test - public void rememberMeLoginWhenParameterSpecifiedThenMatchesNamespace() throws Exception { - this.spring.register(RememberMeParameterConfig.class).autowire(); - MockHttpServletRequestBuilder loginWithRememberme = post("/login").with(rememberMeLogin("rememberMe", true)); - // @formatter:off - Cookie rememberMe = this.mvc.perform(loginWithRememberme) - .andReturn() - .getResponse() - .getCookie("remember-me"); - // @formatter:on - assertThat(rememberMe).isNotNull(); - } - - // SEC-2880 - @Test - public void rememberMeLoginWhenCookieNameDeclaredThenMatchesNamespace() throws Exception { - this.spring.register(RememberMeCookieNameConfig.class).autowire(); - // @formatter:off - Cookie rememberMe = this.mvc.perform(post("/login").with(rememberMeLogin())) - .andReturn() - .getResponse() - .getCookie("rememberMe"); - // @formatter:on - assertThat(rememberMe).isNotNull(); - } - - @Test - public void rememberMeLoginWhenGlobalUserDetailsServiceDeclaredThenMatchesNamespace() throws Exception { - DefaultsUserDetailsServiceWithDaoConfig.USERDETAILS_SERVICE = mock(UserDetailsService.class); - this.spring.register(DefaultsUserDetailsServiceWithDaoConfig.class).autowire(); - this.mvc.perform(post("/login").with(rememberMeLogin())); - verify(DefaultsUserDetailsServiceWithDaoConfig.USERDETAILS_SERVICE).loadUserByUsername("user"); - } - - @Test - public void rememberMeLoginWhenUserDetailsServiceDeclaredThenMatchesNamespace() throws Exception { - UserServiceRefConfig.USERDETAILS_SERVICE = mock(UserDetailsService.class); - this.spring.register(UserServiceRefConfig.class).autowire(); - User user = new User("user", "password", AuthorityUtils.createAuthorityList("ROLE_USER")); - given(UserServiceRefConfig.USERDETAILS_SERVICE.loadUserByUsername("user")).willReturn(user); - this.mvc.perform(post("/login").with(rememberMeLogin())); - verify(UserServiceRefConfig.USERDETAILS_SERVICE).loadUserByUsername("user"); - } - - static RequestPostProcessor rememberMeLogin() { - return rememberMeLogin("remember-me", true); - } - - static RequestPostProcessor rememberMeLogin(String parameterName, boolean parameterValue) { - return (request) -> { - csrf().postProcessRequest(request); - request.setParameter("username", "user"); - request.setParameter("password", "password"); - request.setParameter(parameterName, String.valueOf(parameterValue)); - return request; - }; - } - - @Configuration - @EnableWebSecurity - static class RememberMeConfig extends UsersConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin(withDefaults()) - .rememberMe(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - interface RememberMeServicesWithoutLogoutHandler extends RememberMeServices { - - } - - @Configuration - @EnableWebSecurity - static class RememberMeServicesRefConfig { - - static RememberMeServices REMEMBER_ME_SERVICES; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .rememberMe((me) -> me - .rememberMeServices(REMEMBER_ME_SERVICES)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class AuthSuccessConfig extends UsersConfig { - - static AuthenticationSuccessHandler SUCCESS_HANDLER; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .rememberMe((me) -> me - .authenticationSuccessHandler(SUCCESS_HANDLER)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class WithoutKeyConfig extends UsersConfig { - - @Bean - @Order(0) - SecurityFilterChain withoutKeyFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .securityMatcher("/without-key/**") - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .formLogin((login) -> login - .loginProcessingUrl("/without-key/login")) - .rememberMe(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - @Order(1) - SecurityFilterChain keyFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .formLogin(withDefaults()) - .rememberMe((me) -> me - .key("KeyConfig")); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class TokenRepositoryRefConfig extends UsersConfig { - - static PersistentTokenRepository TOKEN_REPOSITORY; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl() - // tokenRepository.setDataSource(dataSource); - // @formatter:off - http - .formLogin(withDefaults()) - .rememberMe((me) -> me - .tokenRepository(TOKEN_REPOSITORY)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class TokenValiditySecondsConfig extends UsersConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .formLogin(withDefaults()) - .rememberMe((me) -> me - .tokenValiditySeconds(314)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class UseSecureCookieConfig extends UsersConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .rememberMe((me) -> me - .useSecureCookie(true)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class RememberMeParameterConfig extends UsersConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .rememberMe((me) -> me - .rememberMeParameter("rememberMe")); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class RememberMeCookieNameConfig extends UsersConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .rememberMe((me) -> me - .rememberMeCookieName("rememberMe")); - return http.build(); - // @formatter:on - } - - } - - @EnableWebSecurity - @Configuration - static class DefaultsUserDetailsServiceWithDaoConfig { - - static UserDetailsService USERDETAILS_SERVICE; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .rememberMe(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return USERDETAILS_SERVICE; - } - - } - - @Configuration - @EnableWebSecurity - static class UserServiceRefConfig extends UsersConfig { - - static UserDetailsService USERDETAILS_SERVICE; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .rememberMe((me) -> me - .userDetailsService(USERDETAILS_SERVICE)); - return http.build(); - // @formatter:on - } - - } - - static class UsersConfig { - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager( - // @formatter:off - User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build()); - // @formatter:on - } - - } - - @RestController - static class SecurityController { - - @GetMapping("/authentication-class") - String authenticationClass(Authentication authentication) { - return authentication.getClass().getName(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceSessionManagementTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceSessionManagementTests.java deleted file mode 100644 index fa775a34aed..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceSessionManagementTests.java +++ /dev/null @@ -1,524 +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.annotation.web.configurers; - -import java.security.Principal; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationListener; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.Authentication; -import org.springframework.security.core.session.SessionInformation; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.core.session.SessionRegistryImpl; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy; -import org.springframework.security.web.authentication.session.SessionAuthenticationException; -import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; -import org.springframework.security.web.authentication.session.SessionFixationProtectionEvent; -import org.springframework.security.web.session.InvalidSessionStrategy; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.ResultMatcher; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Rob Winch - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class NamespaceSessionManagementTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void authenticateWhenDefaultSessionManagementThenMatchesNamespace() throws Exception { - this.spring.register(SessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class) - .autowire(); - MockHttpSession session = new MockHttpSession(); - String sessionId = session.getId(); - MockHttpServletRequestBuilder request = get("/auth").session(session).with(httpBasic("user", "password")); - // @formatter:off - MvcResult result = this.mvc.perform(request) - .andExpect(session()) - .andReturn(); - // @formatter:on - assertThat(result.getRequest().getSession(false).getId()).isNotEqualTo(sessionId); - } - - @Test - public void authenticateWhenUsingInvalidSessionUrlThenMatchesNamespace() throws Exception { - this.spring.register(CustomSessionManagementConfig.class).autowire(); - MockHttpServletRequestBuilder authRequest = get("/auth").with((request) -> { - request.setRequestedSessionIdValid(false); - request.setRequestedSessionId("id"); - return request; - }); - this.mvc.perform(authRequest).andExpect(redirectedUrl("/invalid-session")); - } - - @Test - public void authenticateWhenUsingExpiredUrlThenMatchesNamespace() throws Exception { - this.spring.register(CustomSessionManagementConfig.class).autowire(); - MockHttpSession session = new MockHttpSession(); - SessionInformation sessionInformation = new SessionInformation(new Object(), session.getId(), new Date(0)); - sessionInformation.expireNow(); - SessionRegistry sessionRegistry = this.spring.getContext().getBean(SessionRegistry.class); - given(sessionRegistry.getSessionInformation(session.getId())).willReturn(sessionInformation); - this.mvc.perform(get("/auth").session(session)).andExpect(redirectedUrl("/expired-session")); - } - - @Test - public void authenticateWhenUsingMaxSessionsThenMatchesNamespace() throws Exception { - this.spring.register(CustomSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class) - .autowire(); - this.mvc.perform(get("/auth").with(httpBasic("user", "password"))).andExpect(status().isOk()); - this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) - .andExpect(redirectedUrl("/session-auth-error")); - } - - @Test - public void authenticateWhenUsingFailureUrlThenMatchesNamespace() throws Exception { - this.spring.register(CustomSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class) - .autowire(); - MockHttpServletRequest mock = spy(MockHttpServletRequest.class); - mock.setSession(new MockHttpSession()); - given(mock.changeSessionId()).willThrow(SessionAuthenticationException.class); - mock.setMethod("GET"); - // @formatter:off - MockHttpServletRequestBuilder authRequest = get("/auth") - .with((request) -> mock) - .with(httpBasic("user", "password")); - // @formatter:on - this.mvc.perform(authRequest).andExpect(redirectedUrl("/session-auth-error")); - } - - @Test - public void authenticateWhenUsingSessionRegistryThenMatchesNamespace() throws Exception { - this.spring.register(CustomSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class) - .autowire(); - SessionRegistry sessionRegistry = this.spring.getContext().getBean(SessionRegistry.class); - MockHttpServletRequestBuilder request = get("/auth").with(httpBasic("user", "password")); - this.mvc.perform(request).andExpect(status().isOk()); - verify(sessionRegistry).registerNewSession(any(String.class), any(Object.class)); - } - - // gh-3371 - @Test - public void authenticateWhenUsingCustomInvalidSessionStrategyThenMatchesNamespace() throws Exception { - this.spring.register(InvalidSessionStrategyConfig.class).autowire(); - MockHttpServletRequestBuilder authRequest = get("/auth").with((request) -> { - request.setRequestedSessionIdValid(false); - request.setRequestedSessionId("id"); - return request; - }); - this.mvc.perform(authRequest).andExpect(status().isOk()); - verifyBean(InvalidSessionStrategy.class).onInvalidSessionDetected(any(HttpServletRequest.class), - any(HttpServletResponse.class)); - } - - @Test - public void authenticateWhenUsingCustomSessionAuthenticationStrategyThenMatchesNamespace() throws Exception { - this.spring.register(RefsSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class) - .autowire(); - MockHttpServletRequestBuilder request = get("/auth").with(httpBasic("user", "password")); - this.mvc.perform(request).andExpect(status().isOk()); - verifyBean(SessionAuthenticationStrategy.class).onAuthentication(any(Authentication.class), - any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void authenticateWhenNoSessionFixationProtectionThenMatchesNamespace() throws Exception { - this.spring - .register(SFPNoneSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class) - .autowire(); - MockHttpSession givenSession = new MockHttpSession(); - String givenSessionId = givenSession.getId(); - // @formatter:off - MockHttpServletRequestBuilder request = get("/auth") - .session(givenSession) - .with(httpBasic("user", "password")); - MockHttpSession resultingSession = (MockHttpSession) this.mvc.perform(request) - .andExpect(status().isOk()) - .andReturn() - .getRequest() - .getSession(false); - // @formatter:on - assertThat(givenSessionId).isEqualTo(resultingSession.getId()); - } - - @Test - public void authenticateWhenMigrateSessionFixationProtectionThenMatchesNamespace() throws Exception { - this.spring - .register(SFPMigrateSessionManagementConfig.class, BasicController.class, UserDetailsServiceConfig.class) - .autowire(); - MockHttpSession givenSession = new MockHttpSession(); - String givenSessionId = givenSession.getId(); - givenSession.setAttribute("name", "value"); - // @formatter:off - MockHttpSession resultingSession = (MockHttpSession) this.mvc.perform(get("/auth") - .session(givenSession) - .with(httpBasic("user", "password"))) - .andExpect(status().isOk()) - .andReturn() - .getRequest() - .getSession(false); - // @formatter:on - assertThat(givenSessionId).isNotEqualTo(resultingSession.getId()); - assertThat(resultingSession.getAttribute("name")).isEqualTo("value"); - } - - // SEC-2913 - @Test - public void authenticateWhenUsingSessionFixationProtectionThenUsesNonNullEventPublisher() throws Exception { - this.spring.register(SFPPostProcessedConfig.class, UserDetailsServiceConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = get("/auth") - .session(new MockHttpSession()) - .with(httpBasic("user", "password")); - // @formatter:on - this.mvc.perform(request).andExpect(status().isNotFound()); - verifyBean(MockEventListener.class).onApplicationEvent(any(SessionFixationProtectionEvent.class)); - } - - @Test - public void authenticateWhenNewSessionFixationProtectionThenMatchesNamespace() throws Exception { - this.spring.register(SFPNewSessionSessionManagementConfig.class, UserDetailsServiceConfig.class).autowire(); - MockHttpSession givenSession = new MockHttpSession(); - String givenSessionId = givenSession.getId(); - givenSession.setAttribute("name", "value"); - MockHttpServletRequestBuilder request = get("/auth").session(givenSession).with(httpBasic("user", "password")); - // @formatter:off - MockHttpSession resultingSession = (MockHttpSession) this.mvc.perform(request) - .andExpect(status().isNotFound()) - .andReturn() - .getRequest() - .getSession(false); - // @formatter:on - assertThat(givenSessionId).isNotEqualTo(resultingSession.getId()); - assertThat(resultingSession.getAttribute("name")).isNull(); - } - - private T verifyBean(Class clazz) { - return verify(this.spring.getContext().getBean(clazz)); - } - - private static SessionResultMatcher session() { - return new SessionResultMatcher(); - } - - @Configuration - @EnableWebSecurity - static class SessionManagementConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .sessionManagement((sessions) -> sessions - .requireExplicitAuthenticationStrategy(false) - ) - .httpBasic(Customizer.withDefaults()); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - static class CustomSessionManagementConfig { - - SessionRegistry sessionRegistry = spy(SessionRegistryImpl.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .httpBasic(withDefaults()) - .sessionManagement((management) -> management - .invalidSessionUrl("/invalid-session") // session-management@invalid-session-url - .sessionAuthenticationErrorUrl("/session-auth-error") // session-management@session-authentication-error-url - .maximumSessions(1) // session-management/concurrency-control@max-sessions - .maxSessionsPreventsLogin(true) // session-management/concurrency-control@error-if-maximum-exceeded - .expiredUrl("/expired-session") // session-management/concurrency-control@expired-url - .sessionRegistry(sessionRegistry())); - return http.build(); // session-management/concurrency-control@session-registry-ref - // @formatter:on - } - - @Bean - SessionRegistry sessionRegistry() { - return this.sessionRegistry; - } - - } - - @Configuration - @EnableWebSecurity - static class InvalidSessionStrategyConfig { - - InvalidSessionStrategy invalidSessionStrategy = mock(InvalidSessionStrategy.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((management) -> management - .invalidSessionStrategy(invalidSessionStrategy())); - return http.build(); - // @formatter:on - } - - @Bean - InvalidSessionStrategy invalidSessionStrategy() { - return this.invalidSessionStrategy; - } - - } - - @Configuration - @EnableWebSecurity - static class RefsSessionManagementConfig { - - SessionAuthenticationStrategy sessionAuthenticationStrategy = mock(SessionAuthenticationStrategy.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((management) -> management - .sessionAuthenticationStrategy(sessionAuthenticationStrategy())) - .httpBasic(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - SessionAuthenticationStrategy sessionAuthenticationStrategy() { - return this.sessionAuthenticationStrategy; - } - - } - - @Configuration - @EnableWebSecurity - static class SFPNoneSessionManagementConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((management) -> management - .sessionAuthenticationStrategy(new NullAuthenticatedSessionStrategy())) - .httpBasic(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class SFPMigrateSessionManagementConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((management) -> management - .requireExplicitAuthenticationStrategy(false)) - .httpBasic(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class SFPPostProcessedConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((sessions) -> sessions - .requireExplicitAuthenticationStrategy(false) - ) - .httpBasic(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - MockEventListener eventListener() { - return spy(new MockEventListener()); - } - - } - - @Configuration - @EnableWebSecurity - static class SFPNewSessionSessionManagementConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((sessions) -> sessions - .sessionFixation().newSession() - .requireExplicitAuthenticationStrategy(false) - ) - .httpBasic(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - static class MockEventListener implements ApplicationListener { - - List events = new ArrayList<>(); - - @Override - public void onApplicationEvent(SessionFixationProtectionEvent event) { - this.events.add(event); - } - - } - - @Configuration - static class UserDetailsServiceConfig { - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager( - // @formatter:off - User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build()); - // @formatter:on - } - - } - - @RestController - static class BasicController { - - @GetMapping("/") - String ok() { - return "ok"; - } - - @GetMapping("/auth") - String auth(Principal principal) { - return principal.getName(); - } - - } - - private static class SessionResultMatcher implements ResultMatcher { - - private String id; - - private Boolean valid; - - private Boolean exists = true; - - ResultMatcher exists(boolean exists) { - this.exists = exists; - return this; - } - - ResultMatcher valid(boolean valid) { - this.valid = valid; - return this.exists(true); - } - - ResultMatcher id(String id) { - this.id = id; - return this.exists(true); - } - - @Override - public void match(MvcResult result) { - if (!this.exists) { - assertThat(result.getRequest().getSession(false)).isNull(); - return; - } - assertThat(result.getRequest().getSession(false)).isNotNull(); - MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); - if (this.valid != null) { - if (this.valid) { - assertThat(session.isInvalid()).isFalse(); - } - else { - assertThat(session.isInvalid()).isTrue(); - } - } - if (this.id != null) { - assertThat(session.getId()).isEqualTo(this.id); - } - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/PasswordManagementConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/PasswordManagementConfigurerTests.java deleted file mode 100644 index 0b65e42d4dd..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/PasswordManagementConfigurerTests.java +++ /dev/null @@ -1,122 +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.annotation.web.configurers; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; - -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link PasswordManagementConfigurer}. - * - * @author Evgeniy Cheban - */ -@ExtendWith(SpringTestContextExtension.class) -public class PasswordManagementConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void whenChangePasswordPageNotSetThenDefaultChangePasswordPageUsed() throws Exception { - this.spring.register(PasswordManagementWithDefaultChangePasswordPageConfig.class).autowire(); - - this.mvc.perform(get("/.well-known/change-password")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/change-password")); - } - - @Test - public void whenChangePasswordPageSetThenSpecifiedChangePasswordPageUsed() throws Exception { - this.spring.register(PasswordManagementWithCustomChangePasswordPageConfig.class).autowire(); - - this.mvc.perform(get("/.well-known/change-password")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/custom-change-password-page")); - } - - @Test - public void whenSettingNullChangePasswordPage() { - PasswordManagementConfigurer configurer = new PasswordManagementConfigurer(); - assertThatIllegalArgumentException().isThrownBy(() -> configurer.changePasswordPage(null)) - .withMessage("changePasswordPage cannot be empty"); - } - - @Test - public void whenSettingEmptyChangePasswordPage() { - PasswordManagementConfigurer configurer = new PasswordManagementConfigurer(); - assertThatIllegalArgumentException().isThrownBy(() -> configurer.changePasswordPage("")) - .withMessage("changePasswordPage cannot be empty"); - } - - @Test - public void whenSettingBlankChangePasswordPage() { - PasswordManagementConfigurer configurer = new PasswordManagementConfigurer(); - assertThatIllegalArgumentException().isThrownBy(() -> configurer.changePasswordPage(" ")) - .withMessage("changePasswordPage cannot be empty"); - } - - @Configuration - @EnableWebSecurity - static class PasswordManagementWithDefaultChangePasswordPageConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - return http - .passwordManagement(withDefaults()) - .build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class PasswordManagementWithCustomChangePasswordPageConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - return http - .passwordManagement((passwordManagement) -> passwordManagement - .changePasswordPage("/custom-change-password-page") - ) - .build(); - // @formatter:on - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/PermitAllSupportTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/PermitAllSupportTests.java deleted file mode 100644 index 6773f4d625a..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/PermitAllSupportTests.java +++ /dev/null @@ -1,141 +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.annotation.web.configurers; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Rob Winch - * @author Josh Cummings - * - */ -@ExtendWith(SpringTestContextExtension.class) -public class PermitAllSupportTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mvc; - - @Test - public void performWhenUsingPermitAllExactUrlRequestMatcherThenMatchesExactUrl() throws Exception { - this.spring.register(PermitAllConfig.class).autowire(); - MockHttpServletRequestBuilder request = get("/app/xyz").contextPath("/app"); - this.mvc.perform(request).andExpect(status().isNotFound()); - MockHttpServletRequestBuilder getWithQuery = get("/app/xyz?def").contextPath("/app"); - this.mvc.perform(getWithQuery).andExpect(status().isFound()); - MockHttpServletRequestBuilder postWithQueryAndCsrf = post("/app/abc?def").with(csrf()).contextPath("/app"); - this.mvc.perform(postWithQueryAndCsrf).andExpect(status().isNotFound()); - MockHttpServletRequestBuilder getWithCsrf = get("/app/abc").with(csrf()).contextPath("/app"); - this.mvc.perform(getWithCsrf).andExpect(status().isFound()); - } - - @Test - public void performWhenUsingPermitAllExactUrlRequestMatcherThenMatchesExactUrlWithAuthorizeHttp() throws Exception { - this.spring.register(PermitAllConfigAuthorizeHttpRequests.class).autowire(); - MockHttpServletRequestBuilder request = get("/app/xyz").contextPath("/app"); - this.mvc.perform(request).andExpect(status().isNotFound()); - MockHttpServletRequestBuilder getWithQuery = get("/app/xyz?def").contextPath("/app"); - this.mvc.perform(getWithQuery).andExpect(status().isFound()); - MockHttpServletRequestBuilder postWithQueryAndCsrf = post("/app/abc?def").with(csrf()).contextPath("/app"); - this.mvc.perform(postWithQueryAndCsrf).andExpect(status().isNotFound()); - MockHttpServletRequestBuilder getWithCsrf = get("/app/abc").with(csrf()).contextPath("/app"); - this.mvc.perform(getWithCsrf).andExpect(status().isFound()); - } - - @Test - public void configureWhenNotAuthorizeRequestsThenException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(NoAuthorizedUrlsConfig.class).autowire()) - .withMessageContaining( - "permitAll only works with HttpSecurity.authorizeHttpRequests(). Please define one."); - } - - @Configuration - @EnableWebSecurity - static class PermitAllConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .formLogin((login) -> login - .loginPage("/xyz").permitAll() - .loginProcessingUrl("/abc?def").permitAll()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class PermitAllConfigAuthorizeHttpRequests { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated()) - .formLogin((login) -> login - .loginPage("/xyz").permitAll() - .loginProcessingUrl("/abc?def").permitAll()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class NoAuthorizedUrlsConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin((login) -> login - .permitAll()); - return http.build(); - // @formatter:on - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/PortMapperConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/PortMapperConfigurerTests.java deleted file mode 100644 index 4129190136a..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/PortMapperConfigurerTests.java +++ /dev/null @@ -1,130 +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.annotation.web.configurers; - -import java.util.Collections; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.web.PortMapperImpl; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; - -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; - -/** - * @author Rob Winch - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class PortMapperConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mockMvc; - - @Test - public void requestWhenPortMapperTwiceInvokedThenDoesNotOverride() throws Exception { - this.spring.register(InvokeTwiceDoesNotOverride.class).autowire(); - this.mockMvc.perform(get("http://localhost:543")).andExpect(redirectedUrl("https://localhost:123")); - } - - @Test - public void requestWhenPortMapperHttpMapsToInLambdaThenRedirectsToHttpsPort() throws Exception { - this.spring.register(HttpMapsToInLambdaConfig.class).autowire(); - this.mockMvc.perform(get("http://localhost:543")).andExpect(redirectedUrl("https://localhost:123")); - } - - @Test - public void requestWhenCustomPortMapperInLambdaThenRedirectsToHttpsPort() throws Exception { - this.spring.register(CustomPortMapperInLambdaConfig.class).autowire(); - this.mockMvc.perform(get("http://localhost:543")).andExpect(redirectedUrl("https://localhost:123")); - } - - @Configuration - @EnableWebSecurity - static class InvokeTwiceDoesNotOverride { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .requiresChannel((channel) -> channel - .anyRequest().requiresSecure()) - .portMapper((mapper) -> mapper - .http(543).mapsTo(123)) - .portMapper(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class HttpMapsToInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .requiresChannel((requiresChannel) -> requiresChannel - .anyRequest().requiresSecure() - ) - .portMapper((portMapper) -> portMapper - .http(543).mapsTo(123) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class CustomPortMapperInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - PortMapperImpl customPortMapper = new PortMapperImpl(); - customPortMapper.setPortMappings(Collections.singletonMap("543", "123")); - // @formatter:off - http - .requiresChannel((requiresChannel) -> requiresChannel - .anyRequest().requiresSecure() - ) - .portMapper((portMapper) -> portMapper - .portMapper(customPortMapper) - ); - return http.build(); - // @formatter:on - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java deleted file mode 100644 index 034fd18fbb2..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java +++ /dev/null @@ -1,706 +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.annotation.web.configurers; - -import java.util.Collections; - -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.UnsatisfiedDependencyException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.authentication.RememberMeAuthenticationToken; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.RememberMeServices; -import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter; -import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices; -import org.springframework.security.web.context.HttpRequestResponseHolder; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.hamcrest.Matchers.startsWith; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; - -/** - * Tests for {@link RememberMeConfigurer} - * - * @author Rob Winch - * @author Eddú Meléndez - * @author Eleftheria Stein - */ -@ExtendWith(SpringTestContextExtension.class) -public class RememberMeConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void postWhenNoUserDetailsServiceThenException() { - assertThatExceptionOfType(UnsatisfiedDependencyException.class) - .isThrownBy(() -> this.spring.register(NullUserDetailsConfig.class).autowire()) - .withMessageContaining("userDetailsService cannot be null"); - } - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnRememberMeAuthenticationFilter() { - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(this.spring.getContext().getBean(ObjectPostProcessorConfig.class).objectPostProcessor) - .postProcess(any(RememberMeAuthenticationFilter.class)); - } - - @Test - public void rememberMeWhenInvokedTwiceThenUsesOriginalUserDetailsService() throws Exception { - given(DuplicateDoesNotOverrideConfig.userDetailsService.loadUserByUsername(anyString())) - .willReturn(new User("user", "password", Collections.emptyList())); - this.spring.register(DuplicateDoesNotOverrideConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = get("/") - .with(httpBasic("user", "password")) - .param("remember-me", "true"); - // @formatter:on - this.mvc.perform(request); - verify(DuplicateDoesNotOverrideConfig.userDetailsService).loadUserByUsername("user"); - } - - @Test - public void rememberMeWhenUserDetailsServiceNotConfiguredThenUsesBean() throws Exception { - this.spring.register(UserDetailsServiceBeanConfig.class).autowire(); - MvcResult mvcResult = this.mvc - .perform(post("/login").with(csrf()) - .param("username", "user") - .param("password", "password") - .param("remember-me", "true")) - .andReturn(); - Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); - // @formatter:off - MockHttpServletRequestBuilder request = get("/abc").cookie(rememberMeCookie); - SecurityMockMvcResultMatchers.AuthenticatedMatcher remembermeAuthentication = authenticated() - .withAuthentication((auth) -> assertThat(auth).isInstanceOf(RememberMeAuthenticationToken.class)); - // @formatter:on - this.mvc.perform(request).andExpect(remembermeAuthentication); - } - - @Test - public void rememberMeWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { - this.spring.register(UserDetailsServiceBeanConfig.class, SecurityContextChangedListenerConfig.class).autowire(); - MvcResult mvcResult = this.mvc - .perform(post("/login").with(csrf()) - .param("username", "user") - .param("password", "password") - .param("remember-me", "true")) - .andReturn(); - Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); - // @formatter:off - MockHttpServletRequestBuilder request = get("/abc").cookie(rememberMeCookie); - SecurityMockMvcResultMatchers.AuthenticatedMatcher remembermeAuthentication = authenticated() - .withAuthentication((auth) -> assertThat(auth).isInstanceOf(RememberMeAuthenticationToken.class)); - // @formatter:on - this.mvc.perform(request).andExpect(remembermeAuthentication); - verify(this.spring.getContext().getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); - } - - @Test - public void loginWhenRememberMeTrueThenRespondsWithRememberMeCookie() throws Exception { - this.spring.register(RememberMeConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password") - .param("remember-me", "true"); - // @formatter:on - this.mvc.perform(request).andExpect(cookie().exists("remember-me")); - } - - @Test - public void getWhenRememberMeCookieThenAuthenticationIsRememberMeAuthenticationToken() throws Exception { - this.spring.register(RememberMeConfig.class).autowire(); - MvcResult mvcResult = this.mvc - .perform(post("/login").with(csrf()) - .param("username", "user") - .param("password", "password") - .param("remember-me", "true")) - .andReturn(); - Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); - // @formatter:off - MockHttpServletRequestBuilder request = get("/abc").cookie(rememberMeCookie); - SecurityMockMvcResultMatchers.AuthenticatedMatcher remembermeAuthentication = authenticated() - .withAuthentication((auth) -> assertThat(auth).isInstanceOf(RememberMeAuthenticationToken.class)); - // @formatter:on - this.mvc.perform(request).andExpect(remembermeAuthentication); - } - - @Test - public void logoutWhenRememberMeCookieThenAuthenticationIsRememberMeCookieExpired() throws Exception { - this.spring.register(RememberMeConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password") - .param("remember-me", "true"); - // @formatter:on - MvcResult mvcResult = this.mvc.perform(loginRequest).andReturn(); - Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); - HttpSession session = mvcResult.getRequest().getSession(); - // @formatter:off - MockHttpServletRequestBuilder logoutRequest = post("/logout") - .with(csrf()) - .cookie(rememberMeCookie) - .session((MockHttpSession) session); - this.mvc.perform(logoutRequest) - .andExpect(redirectedUrl("/login?logout")) - .andExpect(cookie().maxAge("remember-me", 0)); - // @formatter:on - } - - @Test - public void getWhenRememberMeCookieAndLoggedOutThenRedirectsToLogin() throws Exception { - this.spring.register(RememberMeConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password") - .param("remember-me", "true"); - // @formatter:on - MvcResult loginMvcResult = this.mvc.perform(loginRequest).andReturn(); - Cookie rememberMeCookie = loginMvcResult.getResponse().getCookie("remember-me"); - HttpSession session = loginMvcResult.getRequest().getSession(); - // @formatter:off - MockHttpServletRequestBuilder logoutRequest = post("/logout") - .with(csrf()) - .cookie(rememberMeCookie) - .session((MockHttpSession) session); - // @formatter:on - MvcResult logoutMvcResult = this.mvc.perform(logoutRequest).andReturn(); - Cookie expiredRememberMeCookie = logoutMvcResult.getResponse().getCookie("remember-me"); - // @formatter:off - MockHttpServletRequestBuilder expiredRequest = get("/abc") - .with(csrf()) - .cookie(expiredRememberMeCookie); - // @formatter:on - this.mvc.perform(expiredRequest).andExpect(redirectedUrl("/login")); - } - - @Test - public void loginWhenRememberMeConfiguredInLambdaThenRespondsWithRememberMeCookie() throws Exception { - this.spring.register(RememberMeInLambdaConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password") - .param("remember-me", "true"); - // @formatter:on - this.mvc.perform(request).andExpect(cookie().exists("remember-me")); - } - - @Test - public void loginWhenRememberMeTrueAndCookieDomainThenRememberMeCookieHasDomain() throws Exception { - this.spring.register(RememberMeCookieDomainConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password") - .param("remember-me", "true"); - this.mvc.perform(request). - andExpect(cookie().exists("remember-me")) - .andExpect(cookie().domain("remember-me", "spring.io")); - // @formatter:on - } - - @Test - public void loginWhenRememberMeTrueAndCookieDomainInLambdaThenRememberMeCookieHasDomain() throws Exception { - this.spring.register(RememberMeCookieDomainInLambdaConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password") - .param("remember-me", "true"); - this.mvc.perform(loginRequest) - .andExpect(cookie().exists("remember-me")) - .andExpect(cookie().domain("remember-me", "spring.io")); - // @formatter:on - } - - @Test - public void configureWhenRememberMeCookieNameAndRememberMeServicesThenException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(RememberMeCookieNameAndRememberMeServicesConfig.class).autowire()) - .withRootCauseInstanceOf(IllegalArgumentException.class) - .withMessageContaining("Can not set rememberMeCookieName and custom rememberMeServices."); - } - - @Test - public void getWhenRememberMeCookieAndNoKeyConfiguredThenKeyFromRememberMeServicesIsUsed() throws Exception { - this.spring.register(FallbackRememberMeKeyConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password") - .param("remember-me", "true"); - // @formatter:on - MvcResult mvcResult = this.mvc.perform(loginRequest).andReturn(); - Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); - MockHttpServletRequestBuilder requestWithRememberme = get("/abc").cookie(rememberMeCookie); - // @formatter:off - SecurityMockMvcResultMatchers.AuthenticatedMatcher remembermeAuthentication = authenticated() - .withAuthentication((auth) -> assertThat(auth).isInstanceOf(RememberMeAuthenticationToken.class)); - // @formatter:on - this.mvc.perform(requestWithRememberme).andExpect(remembermeAuthentication); - } - - // gh-13104 - @Test - public void getWhenCustomSecurityContextRepositoryThenUses() throws Exception { - this.spring.register(SecurityContextRepositoryConfig.class).autowire(); - SecurityContextRepository repository = this.spring.getContext().getBean(SecurityContextRepository.class); - MvcResult mvcResult = this.mvc - .perform(post("/login").with(csrf()) - .param("username", "user") - .param("password", "password") - .param("remember-me", "true")) - .andReturn(); - Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); - reset(repository); - // @formatter:off - MockHttpServletRequestBuilder request = get("/abc").cookie(rememberMeCookie); - SecurityMockMvcResultMatchers.AuthenticatedMatcher remembermeAuthentication = authenticated() - .withAuthentication((auth) -> assertThat(auth).isInstanceOf(RememberMeAuthenticationToken.class)); - // @formatter:on - this.mvc.perform(request).andExpect(remembermeAuthentication); - verify(repository).saveContext(any(), any(), any()); - } - - @Test - public void rememberMeExpiresSessionWhenSessionManagementMaximumSessionsExceeds() throws Exception { - this.spring.register(RememberMeMaximumSessionsConfig.class).autowire(); - - MockHttpServletRequestBuilder loginRequest = post("/login").with(csrf()) - .param("username", "user") - .param("password", "password") - .param("remember-me", "true"); - MvcResult mvcResult = this.mvc.perform(loginRequest).andReturn(); - Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); - HttpSession session = mvcResult.getRequest().getSession(); - - MockHttpServletRequestBuilder exceedsMaximumSessionsRequest = get("/abc").cookie(rememberMeCookie); - this.mvc.perform(exceedsMaximumSessionsRequest); - - MockHttpServletRequestBuilder sessionExpiredRequest = get("/abc").cookie(rememberMeCookie) - .session((MockHttpSession) session); - this.mvc.perform(sessionExpiredRequest) - .andExpect(content().string(startsWith("This session has been expired"))); - } - - @Configuration - @EnableWebSecurity - static class NullUserDetailsConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin(withDefaults()) - .rememberMe(withDefaults()); - // @formatter:on - return http.build(); - } - - @Autowired - void configure(AuthenticationManagerBuilder auth) { - User user = (User) PasswordEncodedUser.user(); - DaoAuthenticationProvider provider = new DaoAuthenticationProvider( - new InMemoryUserDetailsManager(Collections.singletonList(user))); - // @formatter:off - auth - .authenticationProvider(provider); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ObjectPostProcessorConfig { - - ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .rememberMe((me) -> me - .userDetailsService(new AuthenticationManagerBuilder(this.objectPostProcessor).getDefaultUserDetailsService())); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - @Bean - ObjectPostProcessor objectPostProcessor() { - return this.objectPostProcessor; - } - - } - - static class ReflectingObjectPostProcessor implements ObjectPostProcessor { - - @Override - public O postProcess(O object) { - return object; - } - - } - - @Configuration - @EnableWebSecurity - static class DuplicateDoesNotOverrideConfig { - - static UserDetailsService userDetailsService = mock(UserDetailsService.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .httpBasic(withDefaults()) - .rememberMe((me) -> me - .userDetailsService(userDetailsService)) - .rememberMe(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager( - // @formatter:off - User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build() - // @formatter:on - ); - } - - } - - @Configuration - @EnableWebSecurity - static class UserDetailsServiceBeanConfig { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .rememberMe(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService customUserDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class RememberMeConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin(withDefaults()) - .rememberMe(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class RememberMeInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().hasRole("USER") - ) - .formLogin(withDefaults()) - .rememberMe(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class RememberMeCookieDomainConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin(withDefaults()) - .rememberMe((me) -> me - .rememberMeCookieDomain("spring.io")); - return http.build(); - // @formatter:on - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class RememberMeCookieDomainInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().hasRole("USER") - ) - .formLogin(withDefaults()) - .rememberMe((rememberMe) -> rememberMe - .rememberMeCookieDomain("spring.io") - ); - return http.build(); - // @formatter:on - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class RememberMeCookieNameAndRememberMeServicesConfig { - - static RememberMeServices REMEMBER_ME = mock(RememberMeServices.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin(withDefaults()) - .rememberMe((me) -> me - .rememberMeCookieName("SPRING_COOKIE_DOMAIN") - .rememberMeCookieDomain("spring.io") - .rememberMeServices(REMEMBER_ME)); - return http.build(); - // @formatter:on - } - - @Autowired - void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { - // @formatter:off - auth - .inMemoryAuthentication() - .withUser(PasswordEncodedUser.user()); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class FallbackRememberMeKeyConfig extends RememberMeConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().hasRole("USER")) - .formLogin(withDefaults()) - .rememberMe((me) -> me - .rememberMeServices(new TokenBasedRememberMeServices("key", userDetailsService()))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class RememberMeMaximumSessionsConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().hasRole("USER") - ) - .sessionManagement((sessionManagement) -> sessionManagement - .maximumSessions(1) - ) - .formLogin(withDefaults()) - .rememberMe(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class SecurityContextRepositoryConfig { - - private SecurityContextRepository repository = spy(new SpySecurityContextRepository()); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .securityContext((context) -> context.securityContextRepository(this.repository)) - .formLogin(withDefaults()) - .rememberMe(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - SecurityContextRepository securityContextRepository() { - return this.repository; - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - private static class SpySecurityContextRepository implements SecurityContextRepository { - - SecurityContextRepository delegate = new HttpSessionSecurityContextRepository(); - - @Override - public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) { - return this.delegate.loadContext(requestResponseHolder); - } - - @Override - public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { - this.delegate.saveContext(context, request, response); - } - - @Override - public boolean containsContext(HttpServletRequest request) { - return this.delegate.containsContext(request); - } - - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java deleted file mode 100644 index f4d4849467b..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java +++ /dev/null @@ -1,506 +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.annotation.web.configurers; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.mock.web.MockMultipartFile; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.userdetails.User; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.test.web.servlet.RequestCacheResultMatcher; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.savedrequest.NullRequestCache; -import org.springframework.security.web.savedrequest.RequestCache; -import org.springframework.security.web.savedrequest.RequestCacheAwareFilter; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.RequestBuilder; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; - -/** - * Tests for {@link RequestCacheConfigurer} - * - * @author Rob Winch - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class RequestCacheConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnExceptionTranslationFilter() { - this.spring.register(ObjectPostProcessorConfig.class, DefaultSecurityConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(RequestCacheAwareFilter.class)); - } - - @Test - public void getWhenInvokingExceptionHandlingTwiceThenOriginalEntryPointUsed() throws Exception { - this.spring.register(InvokeTwiceDoesNotOverrideConfig.class).autowire(); - this.mvc.perform(get("/")); - verify(InvokeTwiceDoesNotOverrideConfig.requestCache).getMatchingRequest(any(HttpServletRequest.class), - any(HttpServletResponse.class)); - } - - @Test - public void getWhenBookmarkedUrlIsFaviconIcoThenPostAuthenticationRedirectsToRoot() throws Exception { - this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); - // @formatter:off - MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.ico")) - .andExpect(redirectedUrl("/login")) - .andReturn() - .getRequest() - .getSession(); - // @formatter:on - // ignores favicon.ico - this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); - } - - @Test - public void getWhenBookmarkedUrlIsFaviconPngThenPostAuthenticationRedirectsToRoot() throws Exception { - this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); - // @formatter:off - MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.png")) - .andExpect(redirectedUrl("/login")) - .andReturn() - .getRequest() - .getSession(); - // @formatter:on - // ignores favicon.png - this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); - } - - // SEC-2321 - @Test - public void getWhenBookmarkedRequestIsApplicationJsonThenPostAuthenticationRedirectsToRoot() throws Exception { - this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); - MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON); - // @formatter:off - MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("/login")) - .andReturn() - .getRequest() - .getSession(); - // @formatter:on - // ignores application/json - // This is desirable since JSON requests are typically not invoked directly from - // the browser and we don't want the browser to replay them - this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); - } - - // SEC-2321 - @Test - public void getWhenBookmarkedRequestIsXRequestedWithThenPostAuthenticationRedirectsToRoot() throws Exception { - this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder xRequestedWith = get("/messages") - .header("X-Requested-With", "XMLHttpRequest"); - MockHttpSession session = (MockHttpSession) this.mvc - .perform(xRequestedWith) - .andExpect(redirectedUrl("/login")) - .andReturn() - .getRequest() - .getSession(); - // @formatter:on - this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); - // This is desirable since XHR requests are typically not invoked directly from - // the browser and we don't want the browser to replay them - } - - @Test - public void getWhenBookmarkedRequestIsTextEventStreamThenPostAuthenticationRedirectsToRoot() throws Exception { - this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); - MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, - MediaType.TEXT_EVENT_STREAM); - // @formatter:off - MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("/login")) - .andReturn() - .getRequest() - .getSession(); - // @formatter:on - // ignores text/event-stream - // This is desirable since event-stream requests are typically not invoked - // directly from the browser and we don't want the browser to replay them - this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); - } - - @Test - public void getWhenBookmarkedRequestIsWebSocketThenPostAuthenticationRedirectsToRoot() throws Exception { - this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); - MockHttpServletRequestBuilder request = get("/messages").header("Upgrade", "websocket"); - // @formatter:off - MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("/login")) - .andReturn() - .getRequest() - .getSession(); - // @formatter:on - // ignores websocket - // This is desirable since websocket requests are typically not invoked - // directly from the browser and we don't want the browser to replay them - this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); - } - - @Test - public void getWhenBookmarkedRequestIsAllMediaTypeThenPostAuthenticationRemembers() throws Exception { - this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); - MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.ALL); - // @formatter:off - MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("/login")) - .andReturn() - .getRequest() - .getSession(); - // @formatter:on - this.mvc.perform(formLogin(session)).andExpect(RequestCacheResultMatcher.redirectToCachedRequest()); - } - - @Test - public void getWhenBookmarkedRequestIsTextHtmlThenPostAuthenticationRemembers() throws Exception { - this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); - MockHttpServletRequestBuilder request = get("/messages").header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML); - // @formatter:off - MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("/login")) - .andReturn() - .getRequest() - .getSession(); - // @formatter:on - this.mvc.perform(formLogin(session)).andExpect(RequestCacheResultMatcher.redirectToCachedRequest()); - } - - @Test - public void getWhenBookmarkedRequestIsChromeThenPostAuthenticationRemembers() throws Exception { - this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = get("/messages") - .header(HttpHeaders.ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"); - MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("/login")) - .andReturn() - .getRequest() - .getSession(); - // @formatter:on - this.mvc.perform(formLogin(session)).andExpect(RequestCacheResultMatcher.redirectToCachedRequest()); - } - - @Test - public void getWhenBookmarkedRequestIsRequestedWithAndroidThenPostAuthenticationRemembers() throws Exception { - this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = get("/messages") - .header("X-Requested-With", "com.android"); - MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andExpect(redirectedUrl("/login")) - .andReturn() - .getRequest() - .getSession(); - // @formatter:on - this.mvc.perform(formLogin(session)).andExpect(RequestCacheResultMatcher.redirectToCachedRequest()); - } - - // gh-6102 - @Test - public void getWhenRequestCacheIsDisabledThenExceptionTranslationFilterDoesNotStoreRequest() throws Exception { - this.spring.register(RequestCacheDisabledConfig.class, DefaultSecurityConfig.class).autowire(); - // @formatter:off - MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/bob")) - .andReturn() - .getRequest() - .getSession(); - // @formatter:on - this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); - } - - // SEC-7060 - @Test - public void postWhenRequestIsMultipartThenPostAuthenticationRedirectsToRoot() throws Exception { - this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); - MockMultipartFile aFile = new MockMultipartFile("aFile", "A_FILE".getBytes()); - MockMultipartHttpServletRequestBuilder request = multipart("/upload").file(aFile); - // @formatter:off - MockHttpSession session = (MockHttpSession) this.mvc.perform(request) - .andReturn() - .getRequest() - .getSession(); - // @formatter:on - this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); - } - - @Test - public void getWhenRequestCacheIsDisabledInLambdaThenExceptionTranslationFilterDoesNotStoreRequest() - throws Exception { - this.spring.register(RequestCacheDisabledInLambdaConfig.class, DefaultSecurityConfig.class).autowire(); - // @formatter:off - MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/bob")) - .andReturn() - .getRequest() - .getSession(); - // @formatter:on - this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); - } - - @Test - public void getWhenRequestCacheInLambdaThenRedirectedToCachedPage() throws Exception { - this.spring.register(RequestCacheInLambdaConfig.class, DefaultSecurityConfig.class).autowire(); - // @formatter:off - MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/bob")) - .andReturn() - .getRequest() - .getSession(); - // @formatter:on - this.mvc.perform(formLogin(session)).andExpect(RequestCacheResultMatcher.redirectToCachedRequest()); - } - - @Test - public void getWhenCustomRequestCacheInLambdaThenCustomRequestCacheUsed() throws Exception { - this.spring.register(CustomRequestCacheInLambdaConfig.class, DefaultSecurityConfig.class).autowire(); - // @formatter:off - MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/bob")) - .andReturn() - .getRequest() - .getSession(); - // @formatter:on - this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); - } - - @Test - public void getWhenPathPatternFactoryBeanThenFaviconIcoRedirectsToRoot() throws Exception { - this.spring - .register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class, PathPatternFactoryBeanConfig.class) - .autowire(); - // @formatter:off - MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/favicon.ico")) - .andExpect(redirectedUrl("/login")) - .andReturn() - .getRequest() - .getSession(); - // @formatter:on - // ignores favicon.ico - this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); - } - - private static RequestBuilder formLogin(MockHttpSession session) { - // @formatter:off - return post("/login") - .param("username", "user") - .param("password", "password") - .session(session) - .with(csrf()); - // @formatter:on - } - - @Configuration - @EnableWebSecurity - static class ObjectPostProcessorConfig { - - static ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .requestCache(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - static ObjectPostProcessor objectPostProcessor() { - return objectPostProcessor; - } - - } - - static class ReflectingObjectPostProcessor implements ObjectPostProcessor { - - @Override - public O postProcess(O object) { - return object; - } - - } - - @Configuration - @EnableWebSecurity - static class InvokeTwiceDoesNotOverrideConfig { - - static RequestCache requestCache = mock(RequestCache.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .requestCache((cache) -> cache - .requestCache(requestCache)) - .requestCache(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class RequestCacheDefaultsConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .formLogin(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class RequestCacheDisabledConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .formLogin(Customizer.withDefaults()) - .requestCache(RequestCacheConfigurer::disable); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - static class RequestCacheDisabledInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .formLogin(withDefaults()) - .requestCache(RequestCacheConfigurer::disable); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class RequestCacheInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .formLogin(withDefaults()) - .requestCache(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class CustomRequestCacheInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .formLogin(withDefaults()) - .requestCache((requestCache) -> requestCache - .requestCache(new NullRequestCache()) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultSecurityConfig { - - @Bean - InMemoryUserDetailsManager userDetailsManager() { - // @formatter:off - return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder() - .username("user") - .password("password") - .roles("USER") - .build() - ); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class PathPatternFactoryBeanConfig { - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherConfigurerTests.java deleted file mode 100644 index e93317aeadc..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestMatcherConfigurerTests.java +++ /dev/null @@ -1,116 +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.annotation.web.configurers; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; - -import static org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher.pathPattern; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link HttpSecurity.RequestMatcherConfigurer} - * - * @author Rob Winch - * @author Eleftheria Stein - */ -@ExtendWith(SpringTestContextExtension.class) -public class RequestMatcherConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - // SEC-2908 - @Test - public void authorizeRequestsWhenInvokedMultipleTimesThenChainsPaths() throws Exception { - this.spring.register(Sec2908Config.class).autowire(); - // @formatter:off - this.mvc.perform(get("/oauth/abc")) - .andExpect(status().isForbidden()); - this.mvc.perform(get("/api/abc")) - .andExpect(status().isForbidden()); - // @formatter:on - } - - @Test - public void authorizeRequestsWhenInvokedMultipleTimesInLambdaThenChainsPaths() throws Exception { - this.spring.register(AuthorizeRequestInLambdaConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("/oauth/abc")) - .andExpect(status().isForbidden()); - this.mvc.perform(get("/api/abc")) - .andExpect(status().isForbidden()); - // @formatter:on - } - - @Configuration - @EnableWebSecurity - static class Sec2908Config { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .securityMatchers((security) -> security - .requestMatchers(pathPattern("/api/**"))) - .securityMatchers((security) -> security - .requestMatchers(pathPattern("/oauth/**"))) - .authorizeHttpRequests((requests) -> requests - .anyRequest().denyAll()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class AuthorizeRequestInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .securityMatchers((secure) -> secure - .requestMatchers(pathPattern("/api/**")) - ) - .securityMatchers((securityMatchers) -> securityMatchers - .requestMatchers(pathPattern("/oauth/**")) - ) - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().denyAll() - ); - return http.build(); - // @formatter:on - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SecurityContextConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SecurityContextConfigurerTests.java deleted file mode 100644 index 0a7fc9745cc..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SecurityContextConfigurerTests.java +++ /dev/null @@ -1,309 +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.annotation.web.configurers; - -import java.util.List; -import java.util.stream.Collectors; - -import jakarta.servlet.Filter; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpSession; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.TestDeferredSecurityContext; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.builders.TestHttpSecurities; -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.core.context.SecurityContext; -import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.context.HttpRequestResponseHolder; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.security.web.context.NullSecurityContextRepository; -import org.springframework.security.web.context.SecurityContextHolderFilter; -import org.springframework.security.web.context.SecurityContextPersistenceFilter; -import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; - -/** - * Tests for {@link SecurityContextConfigurer} - * - * @author Rob Winch - * @author Eleftheria Stein - */ -@ExtendWith(SpringTestContextExtension.class) -public class SecurityContextConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnSecurityContextPersistenceFilter() { - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(SecurityContextHolderFilter.class)); - } - - @Test - public void securityContextWhenInvokedTwiceThenUsesOriginalSecurityContextRepository() throws Exception { - this.spring.register(DuplicateDoesNotOverrideConfig.class).autowire(); - given(DuplicateDoesNotOverrideConfig.SCR.loadDeferredContext(any(HttpServletRequest.class))) - .willReturn(new TestDeferredSecurityContext(mock(SecurityContext.class), false)); - this.mvc.perform(get("/")); - verify(DuplicateDoesNotOverrideConfig.SCR).loadDeferredContext(any(HttpServletRequest.class)); - } - - // SEC-2932 - @Test - public void securityContextWhenSecurityContextRepositoryNotConfiguredThenDoesNotThrowException() throws Exception { - this.spring.register(SecurityContextRepositoryDefaultsSecurityContextRepositoryConfig.class).autowire(); - this.mvc.perform(get("/")); - } - - @Test - public void requestWhenSecurityContextWithDefaultsInLambdaThenSessionIsCreated() throws Exception { - this.spring.register(SecurityContextWithDefaultsInLambdaConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(formLogin()).andReturn(); - HttpSession session = mvcResult.getRequest().getSession(false); - assertThat(session).isNotNull(); - } - - @Test - public void requestWhenSecurityContextDisabledInLambdaThenContextNotSavedInSession() throws Exception { - this.spring.register(SecurityContextDisabledInLambdaConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(formLogin()).andReturn(); - HttpSession session = mvcResult.getRequest().getSession(false); - assertThat(session).isNull(); - } - - @Test - public void requestWhenNullSecurityContextRepositoryInLambdaThenContextNotSavedInSession() throws Exception { - this.spring.register(NullSecurityContextRepositoryInLambdaConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(formLogin()).andReturn(); - HttpSession session = mvcResult.getRequest().getSession(false); - assertThat(session).isNull(); - } - - @Test - public void requireExplicitSave() throws Exception { - HttpSessionSecurityContextRepository repository = new HttpSessionSecurityContextRepository(); - SpringTestContext testContext = this.spring.register(RequireExplicitSaveConfig.class); - testContext.autowire(); - FilterChainProxy filterChainProxy = testContext.getContext().getBean(FilterChainProxy.class); - // @formatter:off - List> filterTypes = filterChainProxy.getFilters("/") - .stream() - .map(Filter::getClass) - .collect(Collectors.toList()); - assertThat(filterTypes) - .contains(SecurityContextHolderFilter.class) - .doesNotContain(SecurityContextPersistenceFilter.class); - // @formatter:on - MvcResult mvcResult = this.mvc.perform(formLogin()).andReturn(); - SecurityContext securityContext = repository - .loadContext(new HttpRequestResponseHolder(mvcResult.getRequest(), mvcResult.getResponse())); - assertThat(securityContext.getAuthentication()).isNotNull(); - } - - @Configuration(proxyBeanMethods = false) - @EnableWebSecurity - static class ObjectPostProcessorConfig { - - static ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .securityContext(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - static ObjectPostProcessor objectPostProcessor() { - return objectPostProcessor; - } - - } - - static class ReflectingObjectPostProcessor implements ObjectPostProcessor { - - @Override - public O postProcess(O object) { - return object; - } - - } - - @Configuration - @EnableWebSecurity - static class DuplicateDoesNotOverrideConfig { - - static SecurityContextRepository SCR = mock(SecurityContextRepository.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .securityContext((context) -> context - .securityContextRepository(SCR)) - .securityContext(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class SecurityContextRepositoryDefaultsSecurityContextRepositoryConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - TestHttpSecurities.disableDefaults(http); - // @formatter:off - http - .addFilter(new WebAsyncManagerIntegrationFilter()) - .anonymous(withDefaults()) - .securityContext(withDefaults()) - .authorizeHttpRequests((requests) -> requests - .anyRequest().permitAll()) - .httpBasic(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class SecurityContextWithDefaultsInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .securityContext(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class SecurityContextDisabledInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .securityContext(AbstractHttpConfigurer::disable); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class NullSecurityContextRepositoryInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .securityContext((securityContext) -> securityContext - .securityContextRepository(new NullSecurityContextRepository()) - ); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class RequireExplicitSaveConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .securityContext((securityContext) -> securityContext - .requireExplicitSave(true) - ); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java deleted file mode 100644 index 152c3203693..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ServletApiConfigurerTests.java +++ /dev/null @@ -1,425 +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.annotation.web.configurers; - -import java.util.List; - -import jakarta.servlet.Filter; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationTrustResolver; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.AuthenticationException; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; -import org.springframework.security.util.FieldUtils; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.logout.CompositeLogoutHandler; -import org.springframework.security.web.authentication.logout.LogoutFilter; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler; -import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.context.ConfigurableWebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link ServletApiConfigurer} - * - * @author Rob Winch - * @author Eleftheria Stein - * @author Onur Kagan Ozcan - */ -@ExtendWith(SpringTestContextExtension.class) -public class ServletApiConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnSecurityContextHolderAwareRequestFilter() { - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor) - .postProcess(any(SecurityContextHolderAwareRequestFilter.class)); - } - - // SEC-2215 - @Test - public void configureWhenUsingDefaultsThenAuthenticationManagerIsNotNull() { - this.spring.register(ServletApiConfig.class).autowire(); - assertThat(this.spring.getContext().getBean("customAuthenticationManager")).isNotNull(); - } - - @Test - public void configureWhenUsingDefaultsThenAuthenticationEntryPointIsLogin() throws Exception { - this.spring.register(ServletApiConfig.class).autowire(); - this.mvc.perform(formLogin()).andExpect(status().isFound()); - } - - // SEC-2926 - @Test - public void configureWhenUsingDefaultsThenRolePrefixIsSet() throws Exception { - this.spring.register(ServletApiConfig.class, AdminController.class).autowire(); - TestingAuthenticationToken user = new TestingAuthenticationToken("user", "pass", "ROLE_ADMIN"); - MockHttpServletRequestBuilder request = get("/admin").with(authentication(user)); - this.mvc.perform(request).andExpect(status().isOk()); - } - - @Test - public void requestWhenCustomAuthenticationEntryPointThenEntryPointUsed() throws Exception { - this.spring.register(CustomEntryPointConfig.class).autowire(); - this.mvc.perform(get("/")); - verify(CustomEntryPointConfig.ENTRYPOINT).commence(any(HttpServletRequest.class), - any(HttpServletResponse.class), any(AuthenticationException.class)); - } - - @Test - public void servletApiWhenInvokedTwiceThenUsesOriginalRole() throws Exception { - this.spring.register(DuplicateInvocationsDoesNotOverrideConfig.class, AdminController.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = get("/admin") - .with(user("user").authorities(AuthorityUtils.createAuthorityList("PERMISSION_ADMIN"))); - this.mvc.perform(request) - .andExpect(status().isOk()); - SecurityMockMvcRequestPostProcessors.UserRequestPostProcessor userWithRoleAdmin = user("user") - .authorities(AuthorityUtils.createAuthorityList("ROLE_ADMIN")); - MockHttpServletRequestBuilder requestWithRoleAdmin = get("/admin") - .with(userWithRoleAdmin); - this.mvc.perform(requestWithRoleAdmin) - .andExpect(status().isForbidden()); - // @formatter:on - } - - @Test - public void configureWhenSharedObjectTrustResolverThenTrustResolverUsed() throws Exception { - this.spring.register(SharedTrustResolverConfig.class).autowire(); - this.mvc.perform(get("/")); - verify(SharedTrustResolverConfig.TR, atLeastOnce()).isAnonymous(any()); - } - - @Test - public void requestWhenServletApiWithDefaultsInLambdaThenUsesDefaultRolePrefix() throws Exception { - this.spring.register(ServletApiWithDefaultsInLambdaConfig.class, AdminController.class).autowire(); - MockHttpServletRequestBuilder request = get("/admin") - .with(user("user").authorities(AuthorityUtils.createAuthorityList("ROLE_ADMIN"))); - this.mvc.perform(request).andExpect(status().isOk()); - } - - @Test - public void requestWhenRolePrefixInLambdaThenUsesCustomRolePrefix() throws Exception { - this.spring.register(RolePrefixInLambdaConfig.class, AdminController.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder requestWithAdminPermission = get("/admin") - .with(user("user").authorities(AuthorityUtils.createAuthorityList("PERMISSION_ADMIN"))); - this.mvc.perform(requestWithAdminPermission) - .andExpect(status().isOk()); - MockHttpServletRequestBuilder requestWithAdminRole = get("/admin") - .with(user("user").authorities(AuthorityUtils.createAuthorityList("ROLE_ADMIN"))); - this.mvc.perform(requestWithAdminRole) - .andExpect(status().isForbidden()); - // @formatter:on - } - - @Test - public void checkSecurityContextAwareAndLogoutFilterHasSameSizeAndHasLogoutSuccessEventPublishingLogoutHandler() { - this.spring.register(ServletApiWithLogoutConfig.class); - SecurityContextHolderAwareRequestFilter scaFilter = getFilter(SecurityContextHolderAwareRequestFilter.class); - LogoutFilter logoutFilter = getFilter(LogoutFilter.class); - LogoutHandler lfLogoutHandler = getFieldValue(logoutFilter, "handler"); - assertThat(lfLogoutHandler).isInstanceOf(CompositeLogoutHandler.class); - List scaLogoutHandlers = getFieldValue(scaFilter, "logoutHandlers"); - List lfLogoutHandlers = getFieldValue(lfLogoutHandler, "logoutHandlers"); - assertThat(scaLogoutHandlers).hasSameSizeAs(lfLogoutHandlers); - assertThat(scaLogoutHandlers).hasAtLeastOneElementOfType(LogoutSuccessEventPublishingLogoutHandler.class); - assertThat(lfLogoutHandlers).hasAtLeastOneElementOfType(LogoutSuccessEventPublishingLogoutHandler.class); - } - - @Test - public void logoutServletApiWhenCsrfDisabled() throws Exception { - ConfigurableWebApplicationContext context = this.spring.register(CsrfDisabledConfig.class).getContext(); - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).apply(springSecurity()).build(); - MvcResult mvcResult = mockMvc.perform(get("/")).andReturn(); - assertThat(mvcResult.getRequest().getSession(false)).isNull(); - } - - private T getFilter(Class filterClass) { - return (T) getFilters().stream().filter(filterClass::isInstance).findFirst().orElse(null); - } - - private List getFilters() { - FilterChainProxy proxy = this.spring.getContext().getBean(FilterChainProxy.class); - return proxy.getFilters("/"); - } - - private T getFieldValue(Object target, String fieldName) { - try { - return (T) FieldUtils.getFieldValue(target, fieldName); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - @Configuration - @EnableWebSecurity - static class ObjectPostProcessorConfig { - - static ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .servletApi(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - static ObjectPostProcessor objectPostProcessor() { - return objectPostProcessor; - } - - } - - static class ReflectingObjectPostProcessor implements ObjectPostProcessor { - - @Override - public O postProcess(O object) { - return object; - } - - } - - @Configuration - @EnableWebSecurity - static class ServletApiConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .httpBasic(Customizer.withDefaults()) - .formLogin(Customizer.withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - @Bean - AuthenticationManager customAuthenticationManager(UserDetailsService userDetailsService) { - DaoAuthenticationProvider provider = new DaoAuthenticationProvider(userDetailsService); - return provider::authenticate; - } - - } - - @Configuration - @EnableWebSecurity - static class CustomEntryPointConfig { - - static AuthenticationEntryPoint ENTRYPOINT = spy(AuthenticationEntryPoint.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .exceptionHandling((handling) -> handling - .authenticationEntryPoint(ENTRYPOINT)) - .formLogin(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class DuplicateInvocationsDoesNotOverrideConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .servletApi((api) -> api - .rolePrefix("PERMISSION_")) - .servletApi(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class SharedTrustResolverConfig { - - static AuthenticationTrustResolver TR = spy(AuthenticationTrustResolver.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .setSharedObject(AuthenticationTrustResolver.class, TR); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ServletApiWithDefaultsInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .servletApi(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class RolePrefixInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .servletApi((servletApi) -> servletApi - .rolePrefix("PERMISSION_") - ); - return http.build(); - // @formatter:on - } - - } - - @RestController - static class AdminController { - - @GetMapping("/admin") - void admin(HttpServletRequest request) { - if (!request.isUserInRole("ADMIN")) { - throw new AccessDeniedException("This resource is only available to admins"); - } - } - - } - - @Configuration - @EnableWebSecurity - static class ServletApiWithLogoutConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .servletApi(withDefaults()) - .logout(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class CsrfDisabledConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .csrf((csrf) -> csrf.disable()); - return http.build(); - // @formatter:on - } - - @RestController - static class LogoutController { - - @GetMapping("/") - String logout(HttpServletRequest request) throws ServletException { - request.getSession().setAttribute("foo", "bar"); - request.logout(); - return "logout"; - } - - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerServlet31Tests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerServlet31Tests.java deleted file mode 100644 index a5fac1be4c0..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerServlet31Tests.java +++ /dev/null @@ -1,120 +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.annotation.web.configurers; - -import jakarta.servlet.Filter; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.csrf.CsrfToken; -import org.springframework.security.web.csrf.CsrfTokenRequestHandler; -import org.springframework.security.web.csrf.DeferredCsrfToken; -import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; -import org.springframework.security.web.csrf.XorCsrfTokenRequestAttributeHandler; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.web.servlet.TestMockHttpServletRequests.post; - -/** - * @author Rob Winch - */ -public class SessionManagementConfigurerServlet31Tests { - - MockHttpServletResponse response; - - MockFilterChain chain; - - ConfigurableApplicationContext context; - - Filter springSecurityFilterChain; - - @BeforeEach - public void setup() { - this.response = new MockHttpServletResponse(); - this.chain = new MockFilterChain(); - } - - @AfterEach - public void teardown() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void changeSessionIdThenPreserveParameters() throws Exception { - MockHttpServletRequest request = post("/login").param("username", "user").param("password", "password").build(); - String id = request.getSession().getId(); - request.getSession(); - HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository(); - CsrfTokenRequestHandler handler = new XorCsrfTokenRequestAttributeHandler(); - DeferredCsrfToken deferredCsrfToken = repository.loadDeferredToken(request, this.response); - handler.handle(request, this.response, deferredCsrfToken); - CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); - request.setParameter(token.getParameterName(), token.getToken()); - request.getSession().setAttribute("attribute1", "value1"); - loadConfig(SessionManagementDefaultSessionFixationServlet31Config.class); - this.springSecurityFilterChain.doFilter(request, this.response, this.chain); - assertThat(request.getSession().getId()).isNotEqualTo(id); - assertThat(request.getSession().getAttribute("attribute1")).isEqualTo("value1"); - } - - private void loadConfig(Class... classes) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.register(classes); - context.refresh(); - this.context = context; - this.springSecurityFilterChain = this.context.getBean("springSecurityFilterChain", Filter.class); - } - - @Configuration - @EnableWebSecurity - static class SessionManagementDefaultSessionFixationServlet31Config { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .sessionManagement(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerSessionAuthenticationStrategyTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerSessionAuthenticationStrategyTests.java deleted file mode 100644 index 961473df030..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerSessionAuthenticationStrategyTests.java +++ /dev/null @@ -1,90 +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.annotation.web.configurers; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.Authentication; -import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; -import org.springframework.test.web.servlet.MockMvc; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; - -/** - * @author Joe Grandja - */ -@ExtendWith(SpringTestContextExtension.class) -public class SessionManagementConfigurerSessionAuthenticationStrategyTests { - - @Autowired - private MockMvc mvc; - - public final SpringTestContext spring = new SpringTestContext(this); - - // gh-5763 - @Test - public void requestWhenCustomSessionAuthenticationStrategyProvidedThenCalled() throws Exception { - this.spring.register(CustomSessionAuthenticationStrategyConfig.class).autowire(); - this.mvc.perform(formLogin().user("user").password("password")); - verify(CustomSessionAuthenticationStrategyConfig.customSessionAuthenticationStrategy) - .onAuthentication(any(Authentication.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Configuration - @EnableWebSecurity - static class CustomSessionAuthenticationStrategyConfig { - - static SessionAuthenticationStrategy customSessionAuthenticationStrategy = mock( - SessionAuthenticationStrategy.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .sessionManagement((management) -> management - .sessionAuthenticationStrategy(customSessionAuthenticationStrategy)); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerSessionCreationPolicyTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerSessionCreationPolicyTests.java deleted file mode 100644 index 4f96a99b649..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerSessionCreationPolicyTests.java +++ /dev/null @@ -1,120 +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.annotation.web.configurers; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class SessionManagementConfigurerSessionCreationPolicyTests { - - @Autowired - MockMvc mvc; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - public void getWhenSharedObjectSessionCreationPolicyConfigurationThenOverrides() throws Exception { - this.spring.register(StatelessCreateSessionSharedObjectConfig.class).autowire(); - MvcResult result = this.mvc.perform(get("/")).andReturn(); - assertThat(result.getRequest().getSession(false)).isNull(); - } - - @Test - public void getWhenUserSessionCreationPolicyConfigurationThenOverrides() throws Exception { - this.spring.register(StatelessCreateSessionUserConfig.class).autowire(); - MvcResult result = this.mvc.perform(get("/")).andReturn(); - assertThat(result.getRequest().getSession(false)).isNull(); - } - - @Test - public void getWhenDefaultsThenLoginChallengeCreatesSession() throws Exception { - this.spring.register(DefaultConfig.class, BasicController.class).autowire(); - // @formatter:off - MvcResult result = this.mvc.perform(get("/")) - .andExpect(status().isUnauthorized()) - .andReturn(); - // @formatter:on - assertThat(result.getRequest().getSession(false)).isNotNull(); - } - - @Configuration - @EnableWebSecurity - static class StatelessCreateSessionSharedObjectConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.setSharedObject(SessionCreationPolicy.class, SessionCreationPolicy.STATELESS); - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - static class StatelessCreateSessionUserConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((management) -> management.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); - // @formatter:on - http.setSharedObject(SessionCreationPolicy.class, SessionCreationPolicy.ALWAYS); - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultConfig { - - } - - @RestController - static class BasicController { - - @GetMapping("/") - String root() { - return "ok"; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java deleted file mode 100644 index 2656fcc8f08..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTests.java +++ /dev/null @@ -1,928 +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.annotation.web.configurers; - -import java.io.IOException; - -import jakarta.servlet.DispatcherType; -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Answers; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.authentication.AuthenticationTrustResolver; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.TestDeferredSecurityContext; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.DefaultSecurityFilterChain; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy; -import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy; -import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy; -import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; -import org.springframework.security.web.authentication.session.SessionLimit; -import org.springframework.security.web.context.RequestAttributeSecurityContextRepository; -import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.security.web.savedrequest.RequestCache; -import org.springframework.security.web.session.ConcurrentSessionFilter; -import org.springframework.security.web.session.HttpSessionDestroyedEvent; -import org.springframework.security.web.session.SessionManagementFilter; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.util.WebUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.withSettings; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link SessionManagementConfigurer} - * - * @author Rob Winch - * @author Eleftheria Stein - */ -@ExtendWith(SpringTestContextExtension.class) -public class SessionManagementConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void sessionManagementWhenConfiguredThenDoesNotOverrideRequestCache() throws Exception { - SessionManagementRequestCacheConfig.REQUEST_CACHE = mock(RequestCache.class); - this.spring.register(SessionManagementRequestCacheConfig.class).autowire(); - this.mvc.perform(get("/")); - verify(SessionManagementRequestCacheConfig.REQUEST_CACHE).getMatchingRequest(any(HttpServletRequest.class), - any(HttpServletResponse.class)); - } - - @Test - public void sessionManagementWhenConfiguredThenDoesNotOverrideSecurityContextRepository() throws Exception { - SessionManagementSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPO = mock(SecurityContextRepository.class); - given(SessionManagementSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPO - .loadDeferredContext(any(HttpServletRequest.class))) - .willReturn(new TestDeferredSecurityContext(mock(SecurityContext.class), false)); - this.spring.register(SessionManagementSecurityContextRepositoryConfig.class).autowire(); - this.mvc.perform(get("/")); - } - - @Test - public void sessionManagementWhenSecurityContextRepositoryIsConfiguredThenUseIt() throws Exception { - SessionManagementSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPO = mock(SecurityContextRepository.class); - given(SessionManagementSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPO - .loadDeferredContext(any(HttpServletRequest.class))) - .willReturn(new TestDeferredSecurityContext(mock(SecurityContext.class), false)); - this.spring.register(SessionManagementSecurityContextRepositoryConfig.class).autowire(); - this.mvc.perform(get("/")); - verify(SessionManagementSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPO) - .containsContext(any(HttpServletRequest.class)); - } - - @Test - public void sessionManagementWhenInvokedTwiceThenUsesOriginalSessionCreationPolicy() throws Exception { - this.spring.register(InvokeTwiceDoesNotOverride.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/")).andReturn(); - HttpSession session = mvcResult.getRequest().getSession(false); - assertThat(session).isNull(); - } - - // SEC-2137 - @Test - public void getWhenSessionFixationDisabledAndConcurrencyControlEnabledThenSessionIsNotInvalidated() - throws Exception { - this.spring.register(DisableSessionFixationEnableConcurrencyControlConfig.class).autowire(); - MockHttpSession session = new MockHttpSession(); - String sessionId = session.getId(); - // @formatter:off - MockHttpServletRequestBuilder request = get("/") - .with(httpBasic("user", "password")) - .session(session); - MvcResult mvcResult = this.mvc.perform(request) - .andExpect(status().isNotFound()) - .andReturn(); - // @formatter:on - assertThat(mvcResult.getRequest().getSession().getId()).isEqualTo(sessionId); - } - - @Test - public void authenticateWhenNewSessionFixationProtectionInLambdaThenCreatesNewSession() throws Exception { - this.spring.register(SFPNewSessionInLambdaConfig.class).autowire(); - MockHttpSession givenSession = new MockHttpSession(); - String givenSessionId = givenSession.getId(); - givenSession.setAttribute("name", "value"); - // @formatter:off - MockHttpServletRequestBuilder request = get("/auth") - .session(givenSession) - .with(httpBasic("user", "password")); - MockHttpSession resultingSession = (MockHttpSession) this.mvc.perform(request) - .andExpect(status().isNotFound()) - .andReturn() - .getRequest() - .getSession(false); - // @formatter:on - assertThat(givenSessionId).isNotEqualTo(resultingSession.getId()); - assertThat(resultingSession.getAttribute("name")).isNull(); - } - - @Test - public void loginWhenUserLoggedInAndMaxSessionsIsOneThenLoginPrevented() throws Exception { - this.spring.register(ConcurrencyControlConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder firstRequest = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password"); - this.mvc.perform(firstRequest); - MockHttpServletRequestBuilder secondRequest = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password"); - this.mvc.perform(secondRequest) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?error")); - // @formatter:on - } - - @Test - public void loginWhenUserSessionExpiredAndMaxSessionsIsOneThenLoggedIn() throws Exception { - this.spring.register(ConcurrencyControlConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder firstRequest = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password"); - MvcResult mvcResult = this.mvc.perform(firstRequest) - .andReturn(); - // @formatter:on - HttpSession authenticatedSession = mvcResult.getRequest().getSession(); - this.spring.getContext().publishEvent(new HttpSessionDestroyedEvent(authenticatedSession)); - // @formatter:off - MockHttpServletRequestBuilder secondRequest = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password"); - this.mvc.perform(secondRequest) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/")); - // @formatter:on - } - - @Test - public void loginWhenUserLoggedInAndMaxSessionsOneInLambdaThenLoginPrevented() throws Exception { - this.spring.register(ConcurrencyControlInLambdaConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder firstRequest = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password"); - // @formatter:on - this.mvc.perform(firstRequest); - // @formatter:off - MockHttpServletRequestBuilder secondRequest = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password"); - this.mvc.perform(secondRequest) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?error")); - // @formatter:on - } - - @Test - public void loginWhenAdminUserLoggedInAndSessionLimitIsConfiguredThenLoginSuccessfully() throws Exception { - this.spring.register(ConcurrencyControlWithSessionLimitConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder requestBuilder = post("/login") - .with(csrf()) - .param("username", "admin") - .param("password", "password"); - HttpSession firstSession = this.mvc.perform(requestBuilder) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/")) - .andReturn() - .getRequest() - .getSession(false); - assertThat(firstSession).isNotNull(); - HttpSession secondSession = this.mvc.perform(requestBuilder) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/")) - .andReturn() - .getRequest() - .getSession(false); - assertThat(secondSession).isNotNull(); - // @formatter:on - assertThat(firstSession.getId()).isNotEqualTo(secondSession.getId()); - } - - @Test - public void loginWhenAdminUserLoggedInAndSessionLimitIsConfiguredThenLoginPrevented() throws Exception { - this.spring.register(ConcurrencyControlWithSessionLimitConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder requestBuilder = post("/login") - .with(csrf()) - .param("username", "admin") - .param("password", "password"); - HttpSession firstSession = this.mvc.perform(requestBuilder) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/")) - .andReturn() - .getRequest() - .getSession(false); - assertThat(firstSession).isNotNull(); - HttpSession secondSession = this.mvc.perform(requestBuilder) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/")) - .andReturn() - .getRequest() - .getSession(false); - assertThat(secondSession).isNotNull(); - assertThat(firstSession.getId()).isNotEqualTo(secondSession.getId()); - this.mvc.perform(requestBuilder) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?error")); - // @formatter:on - } - - @Test - public void loginWhenUserLoggedInAndSessionLimitIsConfiguredThenLoginPrevented() throws Exception { - this.spring.register(ConcurrencyControlWithSessionLimitConfig.class).autowire(); - // @formatter:off - MockHttpServletRequestBuilder requestBuilder = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password"); - HttpSession firstSession = this.mvc.perform(requestBuilder) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/")) - .andReturn() - .getRequest() - .getSession(false); - assertThat(firstSession).isNotNull(); - this.mvc.perform(requestBuilder) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?error")); - // @formatter:on - } - - @Test - public void requestWhenSessionCreationPolicyStateLessInLambdaThenNoSessionCreated() throws Exception { - this.spring.register(SessionCreationPolicyStateLessInLambdaConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/")).andReturn(); - HttpSession session = mvcResult.getRequest().getSession(false); - assertThat(session).isNull(); - } - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnSessionManagementFilter() { - ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(SessionManagementFilter.class)); - } - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnConcurrentSessionFilter() { - ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor).postProcess(any(ConcurrentSessionFilter.class)); - } - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnConcurrentSessionControlAuthenticationStrategy() { - ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor) - .postProcess(any(ConcurrentSessionControlAuthenticationStrategy.class)); - } - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnCompositeSessionAuthenticationStrategy() { - ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor) - .postProcess(any(CompositeSessionAuthenticationStrategy.class)); - } - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnRegisterSessionAuthenticationStrategy() { - ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor) - .postProcess(any(RegisterSessionAuthenticationStrategy.class)); - } - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnChangeSessionIdAuthenticationStrategy() { - ObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - verify(ObjectPostProcessorConfig.objectPostProcessor) - .postProcess(any(ChangeSessionIdAuthenticationStrategy.class)); - } - - @Test - public void getWhenAnonymousRequestAndTrustResolverSharedObjectReturnsAnonymousFalseThenSessionIsSaved() - throws Exception { - SharedTrustResolverConfig.TR = mock(AuthenticationTrustResolver.class, - withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS)); - given(SharedTrustResolverConfig.TR.isAnonymous(any())).willReturn(false); - this.spring.register(SharedTrustResolverConfig.class).autowire(); - MvcResult mvcResult = this.mvc.perform(get("/")).andReturn(); - assertThat(mvcResult.getRequest().getSession(false)).isNotNull(); - } - - @Test - public void whenOneSessionRegistryBeanThenUseIt() throws Exception { - SessionRegistryOneBeanConfig.SESSION_REGISTRY = mock(SessionRegistry.class); - this.spring.register(SessionRegistryOneBeanConfig.class).autowire(); - MockHttpSession session = new MockHttpSession(this.spring.getContext().getServletContext()); - this.mvc.perform(get("/").session(session)); - verify(SessionRegistryOneBeanConfig.SESSION_REGISTRY).getSessionInformation(session.getId()); - } - - @Test - public void whenTwoSessionRegistryBeansThenUseNeither() throws Exception { - SessionRegistryTwoBeansConfig.SESSION_REGISTRY_ONE = mock(SessionRegistry.class); - SessionRegistryTwoBeansConfig.SESSION_REGISTRY_TWO = mock(SessionRegistry.class); - this.spring.register(SessionRegistryTwoBeansConfig.class).autowire(); - MockHttpSession session = new MockHttpSession(this.spring.getContext().getServletContext()); - this.mvc.perform(get("/").session(session)); - verifyNoInteractions(SessionRegistryTwoBeansConfig.SESSION_REGISTRY_ONE); - verifyNoInteractions(SessionRegistryTwoBeansConfig.SESSION_REGISTRY_TWO); - } - - @Test - public void whenEnableSessionUrlRewritingTrueThenEncodeNotInvoked() throws Exception { - this.spring.register(EnableUrlRewriteConfig.class).autowire(); - // @formatter:off - this.mvc = MockMvcBuilders.webAppContextSetup(this.spring.getContext()) - .addFilters((request, response, chain) -> { - HttpServletResponse responseToSpy = spy((HttpServletResponse) response); - chain.doFilter(request, responseToSpy); - verify(responseToSpy, atLeastOnce()).encodeRedirectURL(any()); - verify(responseToSpy, atLeastOnce()).encodeURL(any()); - }) - .apply(springSecurity()) - .build(); - // @formatter:on - - this.mvc.perform(get("/")).andExpect(content().string("encoded")); - } - - @Test - public void whenDefaultThenEncodeNotInvoked() throws Exception { - this.spring.register(DefaultUrlRewriteConfig.class).autowire(); - // @formatter:off - this.mvc = MockMvcBuilders.webAppContextSetup(this.spring.getContext()) - .addFilters((request, response, chain) -> { - HttpServletResponse responseToSpy = spy((HttpServletResponse) response); - chain.doFilter(request, responseToSpy); - verify(responseToSpy, never()).encodeRedirectURL(any()); - verify(responseToSpy, never()).encodeURL(any()); - }) - .apply(springSecurity()) - .build(); - // @formatter:on - - this.mvc.perform(get("/")).andExpect(content().string("encoded")); - } - - @Test - public void loginWhenSessionCreationPolicyStatelessThenSecurityContextIsAvailableInRequestAttributes() - throws Exception { - this.spring.register(HttpBasicSessionCreationPolicyStatelessConfig.class).autowire(); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/").with(httpBasic("user", "password"))) - .andExpect(status().isOk()) - .andReturn(); - // @formatter:on - HttpSession session = mvcResult.getRequest().getSession(false); - assertThat(session).isNull(); - SecurityContext securityContext = (SecurityContext) mvcResult.getRequest() - .getAttribute(RequestAttributeSecurityContextRepository.DEFAULT_REQUEST_ATTR_NAME); - assertThat(securityContext).isNotNull(); - } - - /** - * This ensures that if an ErrorDispatch occurs, then the SecurityContextRepository - * defaulted by SessionManagementConfigurer is correct (looks at both Session and - * Request Attributes). - * @throws Exception - */ - @Test - public void gh12070WhenErrorDispatchSecurityContextRepositoryWorks() throws Exception { - Filter errorDispatchFilter = new Filter() { - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { - try { - chain.doFilter(request, response); - } - catch (ServletException ex) { - if (request.getDispatcherType() == DispatcherType.ERROR) { - throw ex; - } - MockHttpServletRequest httpRequest = WebUtils.getNativeRequest(request, - MockHttpServletRequest.class); - httpRequest.setDispatcherType(DispatcherType.ERROR); - // necessary to prevent HttpBasicFilter from invoking again - httpRequest.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/error"); - httpRequest.setRequestURI("/error"); - MockFilterChain mockChain = (MockFilterChain) chain; - mockChain.reset(); - mockChain.doFilter(httpRequest, response); - } - } - }; - this.spring.addFilter(errorDispatchFilter).register(Gh12070IssueConfig.class).autowire(); - - // @formatter:off - this.mvc.perform(get("/500").with(httpBasic("user", "password"))) - .andExpect(status().isInternalServerError()); - // @formatter:on - } - - @Configuration - @EnableWebSecurity - static class Gh12070IssueConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .httpBasic(Customizer.withDefaults()) - .formLogin(Customizer.withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - @RestController - static class ErrorController { - - @GetMapping("/500") - String error() throws ServletException { - throw new ServletException("Error"); - } - - @GetMapping("/error") - ResponseEntity errorHandler() { - return new ResponseEntity<>("error", HttpStatus.INTERNAL_SERVER_ERROR); - } - - } - - } - - @Configuration - @EnableWebSecurity - static class SessionManagementRequestCacheConfig { - - static RequestCache REQUEST_CACHE; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .requestCache((cache) -> cache - .requestCache(REQUEST_CACHE)) - .sessionManagement((management) -> management - .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class SessionManagementSecurityContextRepositoryConfig { - - static SecurityContextRepository SECURITY_CONTEXT_REPO; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .securityContext((context) -> context - .securityContextRepository(SECURITY_CONTEXT_REPO)) - .sessionManagement((management) -> management - .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class InvokeTwiceDoesNotOverride { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((management) -> management - .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .sessionManagement(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class DisableSessionFixationEnableConcurrencyControlConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .httpBasic(withDefaults()) - .sessionManagement((management) -> management - .sessionFixation().none() - .maximumSessions(1)); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class SFPNewSessionInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((sessionManagement) -> sessionManagement - .requireExplicitAuthenticationStrategy(false) - .sessionFixation(SessionManagementConfigurer.SessionFixationConfigurer::newSession) - ) - .httpBasic(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class ConcurrencyControlConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .sessionManagement((management) -> management - .maximumSessions(1) - .maxSessionsPreventsLogin(true)); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class ConcurrencyControlInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .sessionManagement((sessionManagement) -> sessionManagement - .sessionConcurrency((sessionConcurrency) -> sessionConcurrency - .maximumSessions(1) - .maxSessionsPreventsLogin(true) - ) - ); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - } - - @Configuration - @EnableWebSecurity - static class ConcurrencyControlWithSessionLimitConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http, SessionLimit sessionLimit) throws Exception { - // @formatter:off - http - .formLogin(withDefaults()) - .sessionManagement((sessionManagement) -> sessionManagement - .sessionConcurrency((sessionConcurrency) -> sessionConcurrency - .maximumSessions(sessionLimit) - .maxSessionsPreventsLogin(true) - ) - ); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.admin(), PasswordEncodedUser.user()); - } - - @Bean - SessionLimit SessionLimit() { - return (authentication) -> { - if ("admin".equals(authentication.getName())) { - return 2; - } - return 1; - }; - } - - } - - @Configuration - @EnableWebSecurity - static class SessionCreationPolicyStateLessInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((sessionManagement) -> sessionManagement - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ObjectPostProcessorConfig { - - static ObjectPostProcessor objectPostProcessor; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((management) -> management - .maximumSessions(1)); - return http.build(); - // @formatter:on - } - - @Bean - static ObjectPostProcessor objectPostProcessor() { - return objectPostProcessor; - } - - } - - static class ReflectingObjectPostProcessor implements ObjectPostProcessor { - - @Override - public O postProcess(O object) { - return object; - } - - } - - @Configuration - @EnableWebSecurity - static class SharedTrustResolverConfig { - - static AuthenticationTrustResolver TR; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((sessions) -> sessions - .requireExplicitAuthenticationStrategy(false) - ) - .setSharedObject(AuthenticationTrustResolver.class, TR); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class SessionRegistryOneBeanConfig { - - private static SessionRegistry SESSION_REGISTRY; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((management) -> management - .maximumSessions(1)); - return http.build(); - // @formatter:on - } - - @Bean - SessionRegistry sessionRegistry() { - return SESSION_REGISTRY; - } - - } - - @Configuration - @EnableWebSecurity - static class SessionRegistryTwoBeansConfig { - - private static SessionRegistry SESSION_REGISTRY_ONE; - - private static SessionRegistry SESSION_REGISTRY_TWO; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((management) -> management - .maximumSessions(1)); - return http.build(); - // @formatter:on - } - - @Bean - SessionRegistry sessionRegistryOne() { - return SESSION_REGISTRY_ONE; - } - - @Bean - SessionRegistry sessionRegistryTwo() { - return SESSION_REGISTRY_TWO; - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultUrlRewriteConfig { - - @Bean - DefaultSecurityFilterChain configure(HttpSecurity http) throws Exception { - return http.build(); - } - - @Bean - EncodesUrls encodesUrls() { - return new EncodesUrls(); - } - - } - - @Configuration - @EnableWebSecurity - static class EnableUrlRewriteConfig { - - @Bean - DefaultSecurityFilterChain configure(HttpSecurity http) throws Exception { - http.sessionManagement((sessions) -> sessions.enableSessionUrlRewriting(true)); - return http.build(); - } - - @Bean - EncodesUrls encodesUrls() { - return new EncodesUrls(); - } - - } - - @Configuration - @EnableWebSecurity - static class HttpBasicSessionCreationPolicyStatelessConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((sessionManagement) -> sessionManagement - .sessionCreationPolicy(SessionCreationPolicy.STATELESS) - ) - .httpBasic(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); - } - - @Bean - EncodesUrls encodesUrls() { - return new EncodesUrls(); - } - - } - - @RestController - static class EncodesUrls { - - @RequestMapping("/") - String encoded(HttpServletResponse response) { - response.encodeURL("/foo"); - response.encodeRedirectURL("/foo"); - return "encoded"; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTransientAuthenticationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTransientAuthenticationTests.java deleted file mode 100644 index 4dc7c3ee3e6..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurerTransientAuthenticationTests.java +++ /dev/null @@ -1,134 +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.annotation.web.configurers; - -import java.util.Collection; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.Transient; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; - -/** - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class SessionManagementConfigurerTransientAuthenticationTests { - - @Autowired - MockMvc mvc; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - public void postWhenTransientAuthenticationThenNoSessionCreated() throws Exception { - this.spring.register(WithTransientAuthenticationConfig.class).autowire(); - MvcResult result = this.mvc.perform(post("/login")).andReturn(); - assertThat(result.getRequest().getSession(false)).isNull(); - } - - @Test - public void postWhenTransientAuthenticationThenAlwaysSessionOverrides() throws Exception { - this.spring.register(AlwaysCreateSessionConfig.class).autowire(); - MvcResult result = this.mvc.perform(post("/login")).andReturn(); - assertThat(result.getRequest().getSession(false)).isNotNull(); - } - - @Configuration - @EnableWebSecurity - static class WithTransientAuthenticationConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .csrf((csrf) -> csrf.disable()) - .authenticationProvider(new TransientAuthenticationProvider()); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - static class AlwaysCreateSessionConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((management) -> management.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)); - return http.build(); - // @formatter:on - } - - } - - static class TransientAuthenticationProvider implements AuthenticationProvider { - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - return new SomeTransientAuthentication(); - } - - @Override - public boolean supports(Class authentication) { - return true; - } - - } - - @Transient - static class SomeTransientAuthentication extends AbstractAuthenticationToken { - - SomeTransientAuthentication() { - super((Collection) null); - } - - @Override - public Object getCredentials() { - return null; - } - - @Override - public Object getPrincipal() { - return null; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationsTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationsTests.java deleted file mode 100644 index 07fb07daf3b..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/UrlAuthorizationsTests.java +++ /dev/null @@ -1,130 +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.annotation.web.configurers; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Rob Winch - * @author Josh Cummings - * - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class UrlAuthorizationsTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - @WithMockUser(authorities = "ROLE_USER") - public void hasAnyAuthorityWhenAuthoritySpecifiedThenMatchesAuthority() throws Exception { - this.spring.register(RoleConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("/role-user-authority")) - .andExpect(status().isNotFound()); - this.mvc.perform(get("/role-user")) - .andExpect(status().isNotFound()); - this.mvc.perform(get("/role-admin-authority")) - .andExpect(status().isForbidden()); - // @formatter:on - } - - @Test - @WithMockUser(authorities = "ROLE_ADMIN") - public void hasAnyAuthorityWhenAuthoritiesSpecifiedThenMatchesAuthority() throws Exception { - this.spring.register(RoleConfig.class).autowire(); - this.mvc.perform(get("/role-user-admin-authority")).andExpect(status().isNotFound()); - this.mvc.perform(get("/role-user-admin")).andExpect(status().isNotFound()); - this.mvc.perform(get("/role-user-authority")).andExpect(status().isForbidden()); - } - - @Test - @WithMockUser(roles = "USER") - public void hasAnyRoleWhenRoleSpecifiedThenMatchesRole() throws Exception { - this.spring.register(RoleConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("/role-user")) - .andExpect(status().isNotFound()); - this.mvc.perform(get("/role-admin")) - .andExpect(status().isForbidden()); - // @formatter:on - } - - @Test - @WithMockUser(roles = "ADMIN") - public void hasAnyRoleWhenRolesSpecifiedThenMatchesRole() throws Exception { - this.spring.register(RoleConfig.class).autowire(); - this.mvc.perform(get("/role-admin-user")).andExpect(status().isForbidden()); - this.mvc.perform(get("/role-user")).andExpect(status().isForbidden()); - } - - @Test - @WithMockUser(authorities = "USER") - public void hasAnyRoleWhenRoleSpecifiedThenDoesNotMatchAuthority() throws Exception { - this.spring.register(RoleConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("/role-user")) - .andExpect(status().isForbidden()); - this.mvc.perform(get("/role-admin")) - .andExpect(status().isForbidden()); - // @formatter:on - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class RoleConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .requestMatchers("/role-user-authority").hasAnyAuthority("ROLE_USER") - .requestMatchers("/role-admin-authority").hasAnyAuthority("ROLE_ADMIN") - .requestMatchers("/role-user-admin-authority").hasAnyAuthority("ROLE_USER", "ROLE_ADMIN") - .requestMatchers("/role-user").hasAnyRole("USER") - .requestMatchers("/role-admin").hasAnyRole("ADMIN") - .requestMatchers("/role-user-admin").hasAnyRole("USER", "ADMIN")); - return http.build(); - // @formatter:on - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java deleted file mode 100644 index 80b301e96ce..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/WebAuthnConfigurerTests.java +++ /dev/null @@ -1,478 +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.annotation.web.configurers; - -import java.nio.charset.StandardCharsets; -import java.util.List; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpOutputMessage; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.ui.DefaultResourcesFilter; -import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions; -import org.springframework.security.web.webauthn.api.TestPublicKeyCredentialCreationOptions; -import org.springframework.security.web.webauthn.authentication.WebAuthnAuthenticationFilter; -import org.springframework.security.web.webauthn.management.WebAuthnRelyingPartyOperations; -import org.springframework.security.web.webauthn.registration.HttpSessionPublicKeyCredentialCreationOptionsRepository; -import org.springframework.test.web.servlet.MockMvc; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.Mockito.mock; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Daniel Garnier-Moiroux - */ -@ExtendWith(SpringTestContextExtension.class) -public class WebAuthnConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void webauthnWhenConfiguredConfiguredThenServesJavascript() throws Exception { - this.spring.register(DefaultWebauthnConfiguration.class).autowire(); - this.mvc.perform(get("/login/webauthn.js")) - .andExpect(status().isOk()) - .andExpect(header().string("content-type", "text/javascript;charset=UTF-8")) - .andExpect(content().string(containsString("async function authenticate("))); - } - - @Test - public void webauthnWhenConfiguredConfiguredThenServesCss() throws Exception { - this.spring.register(DefaultWebauthnConfiguration.class).autowire(); - this.mvc.perform(get("/default-ui.css")) - .andExpect(status().isOk()) - .andExpect(header().string("content-type", "text/css;charset=UTF-8")) - .andExpect(content().string(containsString("body {"))); - } - - // gh-18128 - @Test - public void webAuthnAuthenticationFilterIsPostProcessed() throws Exception { - this.spring.register(DefaultWebauthnConfiguration.class, PostProcessorConfiguration.class).autowire(); - PostProcessorConfiguration postProcess = this.spring.getContext().getBean(PostProcessorConfiguration.class); - assertThat(postProcess.webauthnFilter).isNotNull(); - } - - @Test - public void webauthnWhenNoFormLoginAndDefaultRegistrationPageConfiguredThenServesJavascript() throws Exception { - this.spring.register(NoFormLoginAndDefaultRegistrationPageConfiguration.class).autowire(); - this.mvc.perform(get("/login/webauthn.js")) - .andExpect(status().isOk()) - .andExpect(header().string("content-type", "text/javascript;charset=UTF-8")) - .andExpect(content().string(containsString("async function authenticate("))); - } - - @Test - public void webauthnWhenNoFormLoginAndDefaultRegistrationPageConfiguredThenServesCss() throws Exception { - this.spring.register(NoFormLoginAndDefaultRegistrationPageConfiguration.class).autowire(); - this.mvc.perform(get("/default-ui.css")) - .andExpect(status().isOk()) - .andExpect(header().string("content-type", "text/css;charset=UTF-8")) - .andExpect(content().string(containsString("body {"))); - } - - @Test - public void webauthnWhenFormLoginAndDefaultRegistrationPageConfiguredThenNoDuplicateFilters() { - this.spring.register(DefaultWebauthnConfiguration.class).autowire(); - FilterChainProxy filterChain = this.spring.getContext().getBean(FilterChainProxy.class); - - List defaultResourcesFilters = filterChain.getFilterChains() - .get(0) - .getFilters() - .stream() - .filter(DefaultResourcesFilter.class::isInstance) - .map(DefaultResourcesFilter.class::cast) - .toList(); - - assertThat(defaultResourcesFilters).map(DefaultResourcesFilter::toString) - .filteredOn((filterDescription) -> filterDescription.contains("login/webauthn.js")) - .hasSize(1); - assertThat(defaultResourcesFilters).map(DefaultResourcesFilter::toString) - .filteredOn((filterDescription) -> filterDescription.contains("default-ui.css")) - .hasSize(1); - } - - @Test - void webauthnWhenConfiguredDefaultsRpNameToRpId() throws Exception { - ObjectMapper mapper = new ObjectMapper(); - this.spring.register(DefaultWebauthnConfiguration.class).autowire(); - String response = this.mvc - .perform(post("/webauthn/register/options").with(csrf()) - .with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user")))) - .andExpect(status().is2xxSuccessful()) - .andReturn() - .getResponse() - .getContentAsString(); - - JsonNode parsedResponse = mapper.readTree(response); - - assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com"); - assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("example.com"); - } - - @Test - void webauthnWhenRpNameConfiguredUsesRpName() throws Exception { - ObjectMapper mapper = new ObjectMapper(); - this.spring.register(CustomRpNameWebauthnConfiguration.class).autowire(); - String response = this.mvc - .perform(post("/webauthn/register/options").with(csrf()) - .with(authentication(new TestingAuthenticationToken("test", "ignored", "ROLE_user")))) - .andExpect(status().is2xxSuccessful()) - .andReturn() - .getResponse() - .getContentAsString(); - - JsonNode parsedResponse = mapper.readTree(response); - - assertThat(parsedResponse.get("rp").get("id").asText()).isEqualTo("example.com"); - assertThat(parsedResponse.get("rp").get("name").asText()).isEqualTo("Test RP Name"); - } - - @Test - public void webauthnWhenConfiguredAndFormLoginThenDoesServesJavascript() throws Exception { - this.spring.register(FormLoginAndNoDefaultRegistrationPageConfiguration.class).autowire(); - this.mvc.perform(get("/login/webauthn.js")) - .andExpect(status().isOk()) - .andExpect(header().string("content-type", "text/javascript;charset=UTF-8")) - .andExpect(content().string(containsString("async function authenticate("))); - } - - @Test - public void webauthnWhenConfiguredAndNoDefaultRegistrationPageThenDoesNotServeJavascript() throws Exception { - this.spring.register(NoDefaultRegistrationPageConfiguration.class).autowire(); - this.mvc.perform(get("/login/webauthn.js")).andExpect(status().isNotFound()); - } - - @Test - public void webauthnWhenConfiguredPublicKeyCredentialCreationOptionsRepository() throws Exception { - TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER"); - SecurityContextHolder.setContext(new SecurityContextImpl(user)); - PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions - .createPublicKeyCredentialCreationOptions() - .build(); - WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class); - ConfigCredentialCreationOptionsRepository.rpOperations = rpOperations; - given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options); - String attrName = "attrName"; - HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository = new HttpSessionPublicKeyCredentialCreationOptionsRepository(); - creationOptionsRepository.setAttrName(attrName); - ConfigCredentialCreationOptionsRepository.creationOptionsRepository = creationOptionsRepository; - this.spring.register(ConfigCredentialCreationOptionsRepository.class).autowire(); - this.mvc.perform(post("/webauthn/register/options")) - .andExpect(status().isOk()) - .andExpect(request().sessionAttribute(attrName, options)); - } - - @Test - public void webauthnWhenConfiguredPublicKeyCredentialCreationOptionsRepositoryBeanPresent() throws Exception { - TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER"); - SecurityContextHolder.setContext(new SecurityContextImpl(user)); - PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions - .createPublicKeyCredentialCreationOptions() - .build(); - WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class); - ConfigCredentialCreationOptionsRepositoryFromBean.rpOperations = rpOperations; - given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options); - String attrName = "attrName"; - HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository = new HttpSessionPublicKeyCredentialCreationOptionsRepository(); - creationOptionsRepository.setAttrName(attrName); - ConfigCredentialCreationOptionsRepositoryFromBean.creationOptionsRepository = creationOptionsRepository; - this.spring.register(ConfigCredentialCreationOptionsRepositoryFromBean.class).autowire(); - this.mvc.perform(post("/webauthn/register/options")) - .andExpect(status().isOk()) - .andExpect(request().sessionAttribute(attrName, options)); - } - - @Test - public void webauthnWhenConfiguredMessageConverter() throws Exception { - TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", "ROLE_USER"); - SecurityContextHolder.setContext(new SecurityContextImpl(user)); - PublicKeyCredentialCreationOptions options = TestPublicKeyCredentialCreationOptions - .createPublicKeyCredentialCreationOptions() - .build(); - WebAuthnRelyingPartyOperations rpOperations = mock(WebAuthnRelyingPartyOperations.class); - ConfigMessageConverter.rpOperations = rpOperations; - given(rpOperations.createPublicKeyCredentialCreationOptions(any())).willReturn(options); - HttpMessageConverter converter = mock(HttpMessageConverter.class); - given(converter.canWrite(any(), any())).willReturn(true); - String expectedBody = "123"; - willAnswer((args) -> { - HttpOutputMessage out = (HttpOutputMessage) args.getArguments()[2]; - out.getBody().write(expectedBody.getBytes(StandardCharsets.UTF_8)); - return null; - }).given(converter).write(any(), any(), any()); - ConfigMessageConverter.converter = converter; - this.spring.register(ConfigMessageConverter.class).autowire(); - this.mvc.perform(post("/webauthn/register/options")) - .andExpect(status().isOk()) - .andExpect(content().string(expectedBody)); - } - - @Configuration - @EnableWebSecurity - static class ConfigCredentialCreationOptionsRepository { - - private static HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository; - - private static WebAuthnRelyingPartyOperations rpOperations; - - @Bean - WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() { - return ConfigCredentialCreationOptionsRepository.rpOperations; - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - return http.csrf(AbstractHttpConfigurer::disable) - .webAuthn((c) -> c.creationOptionsRepository(creationOptionsRepository)) - .build(); - } - - } - - @Configuration - @EnableWebSecurity - static class ConfigCredentialCreationOptionsRepositoryFromBean { - - private static HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository; - - private static WebAuthnRelyingPartyOperations rpOperations; - - @Bean - WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() { - return ConfigCredentialCreationOptionsRepositoryFromBean.rpOperations; - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - @Bean - HttpSessionPublicKeyCredentialCreationOptionsRepository creationOptionsRepository() { - return ConfigCredentialCreationOptionsRepositoryFromBean.creationOptionsRepository; - } - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - return http.csrf(AbstractHttpConfigurer::disable).webAuthn(Customizer.withDefaults()).build(); - } - - } - - @Configuration - @EnableWebSecurity - static class ConfigMessageConverter { - - private static HttpMessageConverter converter; - - private static WebAuthnRelyingPartyOperations rpOperations; - - @Bean - WebAuthnRelyingPartyOperations webAuthnRelyingPartyOperations() { - return ConfigMessageConverter.rpOperations; - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - return http.csrf(AbstractHttpConfigurer::disable).webAuthn((c) -> c.messageConverter(converter)).build(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class PostProcessorConfiguration { - - WebAuthnAuthenticationFilter webauthnFilter; - - @Bean - BeanPostProcessor beanPostProcessor() { - return new BeanPostProcessor() { - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) { - if (bean instanceof WebAuthnAuthenticationFilter filter) { - PostProcessorConfiguration.this.webauthnFilter = filter; - } - return bean; - } - }; - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultWebauthnConfiguration { - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(Customizer.withDefaults()) - .webAuthn((authn) -> authn - .rpId("example.com") - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - static class CustomRpNameWebauthnConfiguration { - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - return http.formLogin(Customizer.withDefaults()) - .webAuthn((webauthn) -> webauthn.rpId("example.com").rpName("Test RP Name")) - .build(); - } - - } - - @Configuration - @EnableWebSecurity - static class NoFormLoginAndDefaultRegistrationPageConfiguration { - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .webAuthn((authn) -> authn - .rpId("spring.io") - .rpName("spring") - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - static class FormLoginAndNoDefaultRegistrationPageConfiguration { - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin(Customizer.withDefaults()) - .webAuthn((authn) -> authn - .rpId("spring.io") - .rpName("spring") - .disableDefaultRegistrationPage(true) - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - static class NoDefaultRegistrationPageConfiguration { - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(); - } - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .formLogin((login) -> login - .loginPage("/custom-login-page") - ) - .webAuthn((authn) -> authn - .rpId("spring.io") - .rpName("spring") - .disableDefaultRegistrationPage(true) - ); - // @formatter:on - return http.build(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java deleted file mode 100644 index 5e490152f7b..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java +++ /dev/null @@ -1,444 +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.annotation.web.configurers; - -import java.io.InputStream; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.core.io.ClassPathResource; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.context.SecurityContextChangedListener; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; -import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor; -import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; -import org.springframework.security.web.authentication.preauth.x509.X509TestUtils; -import org.springframework.test.web.servlet.MockMvc; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509; -import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; - -/** - * Tests for {@link X509Configurer} - * - * @author Rob Winch - * @author Eleftheria Stein - */ -@ExtendWith(SpringTestContextExtension.class) -public class X509ConfigurerTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void configureWhenRegisteringObjectPostProcessorThenInvokedOnX509AuthenticationFilter() { - this.spring.register(ObjectPostProcessorConfig.class).autowire(); - ObjectPostProcessor objectPostProcessor = this.spring.getContext().getBean(ObjectPostProcessor.class); - verify(objectPostProcessor).postProcess(any(X509AuthenticationFilter.class)); - } - - @Test - public void x509WhenInvokedTwiceThenUsesOriginalSubjectPrincipalRegex() throws Exception { - this.spring.register(DuplicateDoesNotOverrideConfig.class).autowire(); - X509Certificate certificate = loadCert("rodatexampledotcom.cer"); - // @formatter:off - this.mvc.perform(get("/").with(x509(certificate))) - .andExpect(authenticated().withUsername("rod")); - // @formatter:on - } - - @Test - public void x509WhenConfiguredInLambdaThenUsesDefaults() throws Exception { - this.spring.register(DefaultsInLambdaConfig.class).autowire(); - X509Certificate certificate = loadCert("rod.cer"); - // @formatter:off - this.mvc.perform(get("/").with(x509(certificate))) - .andExpect(authenticated().withUsername("rod")); - // @formatter:on - } - - @Test - public void x509WhenCustomSecurityContextHolderStrategyThenUses() throws Exception { - this.spring.register(DefaultsInLambdaConfig.class, SecurityContextChangedListenerConfig.class).autowire(); - X509Certificate certificate = loadCert("rod.cer"); - // @formatter:off - this.mvc.perform(get("/").with(x509(certificate))) - .andExpect(authenticated().withUsername("rod")); - // @formatter:on - SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); - verify(strategy, atLeastOnce()).getContext(); - SecurityContextChangedListener listener = this.spring.getContext() - .getBean(SecurityContextChangedListener.class); - verify(listener).securityContextChanged(setAuthentication(PreAuthenticatedAuthenticationToken.class)); - } - - @Test - public void x509WhenSubjectPrincipalRegexInLambdaThenUsesRegexToExtractPrincipal() throws Exception { - this.spring.register(SubjectPrincipalRegexInLambdaConfig.class).autowire(); - X509Certificate certificate = loadCert("rodatexampledotcom.cer"); - // @formatter:off - this.mvc.perform(get("/").with(x509(certificate))) - .andExpect(authenticated().withUsername("rod")); - // @formatter:on - } - - @Test - public void x509WhenUserDetailsServiceNotConfiguredThenUsesBean() throws Exception { - this.spring.register(UserDetailsServiceBeanConfig.class).autowire(); - X509Certificate certificate = loadCert("rod.cer"); - // @formatter:off - this.mvc.perform(get("/").with(x509(certificate))) - .andExpect(authenticated().withUsername("rod")); - // @formatter:on - } - - @Test - public void x509WhenUserDetailsServiceAndBeanConfiguredThenDoesNotUseBean() throws Exception { - this.spring.register(UserDetailsServiceAndBeanConfig.class).autowire(); - X509Certificate certificate = loadCert("rod.cer"); - // @formatter:off - this.mvc.perform(get("/").with(x509(certificate))) - .andExpect(authenticated().withUsername("rod")); - // @formatter:on - } - - // gh-13008 - @Test - public void x509WhenStatelessSessionManagementThenDoesNotCreateSession() throws Exception { - this.spring.register(StatelessSessionManagementConfig.class).autowire(); - X509Certificate certificate = loadCert("rodatexampledotcom.cer"); - // @formatter:off - this.mvc.perform(get("/").with(x509(certificate))) - .andExpect((result) -> assertThat(result.getRequest().getSession(false)).isNull()) - .andExpect(authenticated().withUsername("rod")); - // @formatter:on - } - - @Test - public void x509WhenSubjectX500PrincipalExtractor() throws Exception { - this.spring.register(SubjectX500PrincipalExtractorConfig.class).autowire(); - X509Certificate certificate = loadCert("rod.cer"); - // @formatter:off - this.mvc.perform(get("/").with(x509(certificate))) - .andExpect((result) -> assertThat(result.getRequest().getSession(false)).isNull()) - .andExpect(authenticated().withUsername("rod")); - // @formatter:on - } - - @Test - public void x509WhenSubjectX500PrincipalExtractorBean() throws Exception { - this.spring.register(SubjectX500PrincipalExtractorEmailConfig.class).autowire(); - X509Certificate certificate = X509TestUtils.buildTestCertificate(); - // @formatter:off - this.mvc.perform(get("/").with(x509(certificate))) - .andExpect((result) -> assertThat(result.getRequest().getSession(false)).isNull()) - .andExpect(authenticated().withUsername("luke@monkeymachine")); - // @formatter:on - } - - private T loadCert(String location) { - try (InputStream is = new ClassPathResource(location).getInputStream()) { - CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); - return (T) certFactory.generateCertificate(is); - } - catch (Exception ex) { - throw new IllegalArgumentException(ex); - } - } - - @Configuration - @EnableWebSecurity - static class ObjectPostProcessorConfig { - - ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .x509(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - @Primary - ObjectPostProcessor objectPostProcessor() { - return this.objectPostProcessor; - } - - } - - static class ReflectingObjectPostProcessor implements ObjectPostProcessor { - - @Override - public O postProcess(O object) { - return object; - } - - } - - @Configuration - @EnableWebSecurity - static class DuplicateDoesNotOverrideConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .x509((x509) -> x509 - .subjectPrincipalRegex("CN=(.*?)@example.com(?:,|$)")) - .x509(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("rod") - .password("password") - .roles("USER", "ADMIN") - .build(); - return new InMemoryUserDetailsManager(user); - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultsInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .x509(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("rod") - .password("password") - .roles("USER", "ADMIN") - .build(); - return new InMemoryUserDetailsManager(user); - } - - } - - @Configuration - @EnableWebSecurity - static class SubjectPrincipalRegexInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .x509((x509) -> x509 - .subjectPrincipalRegex("CN=(.*?)@example.com(?:,|$)") - ); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("rod") - .password("password") - .roles("USER", "ADMIN") - .build(); - return new InMemoryUserDetailsManager(user); - } - - } - - @Configuration - @EnableWebSecurity - static class UserDetailsServiceBeanConfig { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .x509(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - // @formatter:off - return new InMemoryUserDetailsManager( - User.withDefaultPasswordEncoder() - .username("rod") - .password("password") - .roles("USER", "ADMIN") - .build() - ); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class UserDetailsServiceAndBeanConfig { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - UserDetailsService customUserDetailsService = new InMemoryUserDetailsManager( - User.withDefaultPasswordEncoder() - .username("rod") - .password("password") - .roles("USER", "ADMIN") - .build()); - http - .x509((x509) -> x509 - .userDetailsService(customUserDetailsService) - ); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - // @formatter:off - return mock(UserDetailsService.class); - } - - } - - @Configuration - @EnableWebSecurity - static class StatelessSessionManagementConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .x509((x509) -> x509.subjectPrincipalRegex("CN=(.*?)@example.com(?:,|$)")); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("rod") - .password("password") - .roles("USER", "ADMIN") - .build(); - return new InMemoryUserDetailsManager(user); - } - - } - - @Configuration - @EnableWebSecurity - static class SubjectX500PrincipalExtractorConfig { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .x509((x509) -> x509 - .x509PrincipalExtractor(new SubjectX500PrincipalExtractor()) - ); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("rod") - .password("password") - .roles("USER", "ADMIN") - .build(); - return new InMemoryUserDetailsManager(user); - } - - } - - @Configuration - @EnableWebSecurity - static class SubjectX500PrincipalExtractorEmailConfig { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - SubjectX500PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor(); - principalExtractor.setExtractPrincipalNameFromEmail(true); - // @formatter:off - http - .x509((x509) -> x509 - .x509PrincipalExtractor(principalExtractor) - ); - // @formatter:on - return http.build(); - } - - @Bean - UserDetailsService userDetailsService() { - UserDetails user = User.withDefaultPasswordEncoder() - .username("luke@monkeymachine") - .password("password") - .roles("USER", "ADMIN") - .build(); - return new InMemoryUserDetailsManager(user); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java deleted file mode 100644 index 2a2abf6af06..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java +++ /dev/null @@ -1,479 +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.annotation.web.configurers.oauth2.client; - -import java.util.HashMap; -import java.util.Map; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.oauth2.client.InMemoryOAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; -import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver; -import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.web.DefaultRedirectStrategy; -import org.springframework.security.web.RedirectStrategy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.savedrequest.RequestCache; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link OAuth2ClientConfigurer}. - * - * @author Joe Grandja - * @author Parikshit Dutta - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2ClientConfigurerTests { - - private static ClientRegistrationRepository clientRegistrationRepository; - - private static OAuth2AuthorizedClientService authorizedClientService; - - private static OAuth2AuthorizedClientRepository authorizedClientRepository; - - private static OAuth2AuthorizationRequestResolver authorizationRequestResolver; - - private static RedirectStrategy authorizationRedirectStrategy; - - private static OAuth2AccessTokenResponseClient accessTokenResponseClient; - - private static RequestCache requestCache; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mockMvc; - - private ClientRegistration registration1; - - @BeforeEach - public void setup() { - // @formatter:off - this.registration1 = TestClientRegistrations.clientRegistration() - .registrationId("registration-1") - .clientId("client-1") - .clientSecret("secret") - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .redirectUri("{baseUrl}/client-1") - .scope("user") - .authorizationUri("https://provider.com/oauth2/authorize") - .tokenUri("https://provider.com/oauth2/token") - .userInfoUri("https://provider.com/oauth2/user") - .userNameAttributeName("id") - .clientName("client-1") - .build(); - // @formatter:on - clientRegistrationRepository = new InMemoryClientRegistrationRepository(this.registration1); - authorizedClientService = new InMemoryOAuth2AuthorizedClientService(clientRegistrationRepository); - authorizedClientRepository = new AuthenticatedPrincipalOAuth2AuthorizedClientRepository( - authorizedClientService); - authorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, - "/oauth2/authorization"); - authorizationRedirectStrategy = new DefaultRedirectStrategy(); - OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("access-token-1234") - .tokenType(OAuth2AccessToken.TokenType.BEARER) - .expiresIn(300) - .build(); - accessTokenResponseClient = mock(OAuth2AccessTokenResponseClient.class); - given(accessTokenResponseClient.getTokenResponse(any(OAuth2AuthorizationCodeGrantRequest.class))) - .willReturn(accessTokenResponse); - requestCache = mock(RequestCache.class); - } - - @Test - public void configureWhenAuthorizationCodeRequestThenRedirectForAuthorization() throws Exception { - this.spring.register(OAuth2ClientConfig.class).autowire(); - // @formatter:off - MvcResult mvcResult = this.mockMvc.perform(get("/oauth2/authorization/registration-1")) - .andExpect(status().is3xxRedirection()).andReturn(); - assertThat(mvcResult.getResponse().getRedirectedUrl()) - .matches("https://provider.com/oauth2/authorize\\?" + "response_type=code&client_id=client-1&" - + "scope=user&state=.{15,}&" + "redirect_uri=http://localhost/client-1&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256"); - // @formatter:on - } - - @Test - public void configureWhenOauth2ClientInLambdaThenRedirectForAuthorization() throws Exception { - this.spring.register(OAuth2ClientInLambdaConfig.class).autowire(); - MvcResult mvcResult = this.mockMvc.perform(get("/oauth2/authorization/registration-1")) - .andExpect(status().is3xxRedirection()) - .andReturn(); - assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?" - + "response_type=code&client_id=client-1&" + "scope=user&state=.{15,}&" - + "redirect_uri=http://localhost/client-1&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256"); - } - - @Test - public void configureWhenAuthorizationCodeResponseSuccessThenAuthorizedClientSaved() throws Exception { - this.spring.register(OAuth2ClientConfig.class).autowire(); - // Setup the Authorization Request in the session - Map attributes = new HashMap<>(); - attributes.put(OAuth2ParameterNames.REGISTRATION_ID, this.registration1.getRegistrationId()); - // @formatter:off - OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode() - .authorizationUri(this.registration1.getProviderDetails().getAuthorizationUri()) - .clientId(this.registration1.getClientId()) - .redirectUri("http://localhost/client-1") - .state("state") - .attributes(attributes) - .build(); - // @formatter:on - AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository(); - MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); - MockHttpServletResponse response = new MockHttpServletResponse(); - authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response); - MockHttpSession session = (MockHttpSession) request.getSession(); - String principalName = "user1"; - TestingAuthenticationToken authentication = new TestingAuthenticationToken(principalName, "password"); - // @formatter:off - MockHttpServletRequestBuilder clientRequest = get("/client-1") - .param(OAuth2ParameterNames.CODE, "code") - .param(OAuth2ParameterNames.STATE, "state") - .with(authentication(authentication)) - .session(session); - this.mockMvc.perform(clientRequest) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/client-1")); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = authorizedClientRepository - .loadAuthorizedClient(this.registration1.getRegistrationId(), authentication, request); - assertThat(authorizedClient).isNotNull(); - } - - @Test - public void configureWhenRequestCacheProvidedAndClientAuthorizationRequiredExceptionThrownThenRequestCacheUsed() - throws Exception { - this.spring.register(OAuth2ClientConfig.class).autowire(); - MvcResult mvcResult = this.mockMvc.perform(get("/resource1").with(user("user1"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://provider.com/oauth2/authorize\\?" - + "response_type=code&client_id=client-1&" + "scope=user&state=.{15,}&" - + "redirect_uri=http://localhost/client-1&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256"); - verify(requestCache).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void configureWhenRequestCacheProvidedAndClientAuthorizationSucceedsThenRequestCacheUsed() throws Exception { - this.spring.register(OAuth2ClientConfig.class).autowire(); - // Setup the Authorization Request in the session - Map attributes = new HashMap<>(); - attributes.put(OAuth2ParameterNames.REGISTRATION_ID, this.registration1.getRegistrationId()); - // @formatter:off - OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode() - .authorizationUri(this.registration1.getProviderDetails().getAuthorizationUri()) - .clientId(this.registration1.getClientId()).redirectUri("http://localhost/client-1") - .state("state") - .attributes(attributes) - .build(); - // @formatter:on - AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository(); - MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); - MockHttpServletResponse response = new MockHttpServletResponse(); - authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response); - MockHttpSession session = (MockHttpSession) request.getSession(); - String principalName = "user1"; - TestingAuthenticationToken authentication = new TestingAuthenticationToken(principalName, "password"); - // @formatter:off - MockHttpServletRequestBuilder clientRequest = get("/client-1") - .param(OAuth2ParameterNames.CODE, "code") - .param(OAuth2ParameterNames.STATE, "state") - .with(authentication(authentication)) - .session(session); - this.mockMvc.perform(clientRequest) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/client-1")); - // @formatter:on - verify(requestCache).getRequest(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - // gh-5521 - @Test - public void configureWhenCustomAuthorizationRequestResolverSetThenAuthorizationRequestIncludesCustomParameters() - throws Exception { - // Override default resolver - OAuth2AuthorizationRequestResolver defaultAuthorizationRequestResolver = authorizationRequestResolver; - authorizationRequestResolver = mock(OAuth2AuthorizationRequestResolver.class); - given(authorizationRequestResolver.resolve(any())) - .willAnswer((invocation) -> defaultAuthorizationRequestResolver.resolve(invocation.getArgument(0))); - this.spring.register(OAuth2ClientConfig.class).autowire(); - // @formatter:off - this.mockMvc.perform(get("/oauth2/authorization/registration-1")) - .andExpect(status().is3xxRedirection()) - .andReturn(); - // @formatter:on - verify(authorizationRequestResolver).resolve(any()); - } - - @Test - public void configureWhenCustomAuthorizationRedirectStrategySetThenAuthorizationRedirectStrategyUsed() - throws Exception { - authorizationRedirectStrategy = mock(RedirectStrategy.class); - this.spring.register(OAuth2ClientConfig.class).autowire(); - // @formatter:off - this.mockMvc.perform(get("/oauth2/authorization/registration-1")) - .andExpect(status().isOk()) - .andReturn(); - // @formatter:on - verify(authorizationRedirectStrategy).sendRedirect(any(), any(), anyString()); - } - - @Test - public void configureWhenCustomAuthorizationRequestResolverBeanPresentThenAuthorizationRequestResolverUsed() - throws Exception { - OAuth2AuthorizationRequestResolver defaultAuthorizationRequestResolver = authorizationRequestResolver; - authorizationRequestResolver = mock(OAuth2AuthorizationRequestResolver.class); - given(authorizationRequestResolver.resolve(any())) - .willAnswer((invocation) -> defaultAuthorizationRequestResolver.resolve(invocation.getArgument(0))); - this.spring.register(OAuth2ClientInLambdaConfig.class, AuthorizationRequestResolverConfig.class).autowire(); - // @formatter:off - this.mockMvc.perform(get("/oauth2/authorization/registration-1")) - .andExpect(status().is3xxRedirection()) - .andReturn(); - // @formatter:on - verify(authorizationRequestResolver).resolve(any()); - } - - @Test - public void configureWhenOAuth2LoginBeansConfiguredThenNotShared() throws Exception { - this.spring.register(OAuth2ClientConfigWithOAuth2Login.class).autowire(); - // Setup the Authorization Request in the session - Map attributes = new HashMap<>(); - attributes.put(OAuth2ParameterNames.REGISTRATION_ID, this.registration1.getRegistrationId()); - // @formatter:off - OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode() - .authorizationUri(this.registration1.getProviderDetails().getAuthorizationUri()) - .clientId(this.registration1.getClientId()) - .redirectUri("http://localhost/client-1") - .state("state") - .attributes(attributes) - .build(); - // @formatter:on - AuthorizationRequestRepository authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository(); - MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); - MockHttpServletResponse response = new MockHttpServletResponse(); - authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response); - MockHttpSession session = (MockHttpSession) request.getSession(); - String principalName = "user1"; - TestingAuthenticationToken authentication = new TestingAuthenticationToken(principalName, "password"); - // @formatter:off - MockHttpServletRequestBuilder clientRequest = get("/client-1") - .param(OAuth2ParameterNames.CODE, "code") - .param(OAuth2ParameterNames.STATE, "state") - .with(authentication(authentication)) - .session(session); - this.mockMvc.perform(clientRequest) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("http://localhost/client-1")); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = authorizedClientRepository - .loadAuthorizedClient(this.registration1.getRegistrationId(), authentication, request); - assertThat(authorizedClient).isNotNull(); - // Ensure shared objects set for OAuth2 Client are not used - ClientRegistrationRepository clientRegistrationRepository = this.spring.getContext() - .getBean(ClientRegistrationRepository.class); - OAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext() - .getBean(OAuth2AuthorizedClientRepository.class); - verifyNoInteractions(clientRegistrationRepository, authorizedClientRepository); - } - - @EnableWebSecurity - @Configuration - @EnableWebMvc - static class OAuth2ClientConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .requestCache((cache) -> cache - .requestCache(requestCache)) - .oauth2Client((client) -> client - .authorizationCodeGrant((code) -> code - .authorizationRequestResolver(authorizationRequestResolver) - .authorizationRedirectStrategy(authorizationRedirectStrategy) - .accessTokenResponseClient(accessTokenResponseClient))); - return http.build(); - // @formatter:on - } - - @Bean - ClientRegistrationRepository clientRegistrationRepository() { - return clientRegistrationRepository; - } - - @Bean - OAuth2AuthorizedClientRepository authorizedClientRepository() { - return authorizedClientRepository; - } - - @RestController - class ResourceController { - - @GetMapping("/resource1") - String resource1( - @RegisteredOAuth2AuthorizedClient("registration-1") OAuth2AuthorizedClient authorizedClient) { - return "resource1"; - } - - } - - } - - @EnableWebSecurity - @Configuration - @EnableWebMvc - static class OAuth2ClientInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .oauth2Client(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - ClientRegistrationRepository clientRegistrationRepository() { - return clientRegistrationRepository; - } - - @Bean - OAuth2AuthorizedClientRepository authorizedClientRepository() { - return authorizedClientRepository; - } - - } - - @Configuration - static class AuthorizationRequestResolverConfig { - - @Bean - OAuth2AuthorizationRequestResolver authorizationRequestResolver() { - return authorizationRequestResolver; - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class OAuth2ClientConfigWithOAuth2Login { - - private final ClientRegistrationRepository clientRegistrationRepository = mock( - ClientRegistrationRepository.class); - - private final OAuth2AuthorizedClientRepository authorizedClientRepository = mock( - OAuth2AuthorizedClientRepository.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .oauth2Client((oauth2Client) -> oauth2Client - .clientRegistrationRepository(OAuth2ClientConfigurerTests.clientRegistrationRepository) - .authorizedClientService(OAuth2ClientConfigurerTests.authorizedClientService) - .authorizationCodeGrant((authorizationCode) -> authorizationCode - .authorizationRequestResolver(authorizationRequestResolver) - .authorizationRedirectStrategy(authorizationRedirectStrategy) - .accessTokenResponseClient(accessTokenResponseClient) - ) - ) - .oauth2Login((oauth2Login) -> oauth2Login - .clientRegistrationRepository(this.clientRegistrationRepository) - .authorizedClientRepository(this.authorizedClientRepository) - ); - // @formatter:on - return http.build(); - } - - @Bean - ClientRegistrationRepository clientRegistrationRepository() { - return this.clientRegistrationRepository; - } - - @Bean - OAuth2AuthorizedClientRepository authorizedClientRepository() { - return this.authorizedClientRepository; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java deleted file mode 100644 index 2bb49d775c8..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java +++ /dev/null @@ -1,1459 +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.annotation.web.configurers.oauth2.client; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -import org.apache.http.HttpHeaders; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mockito; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.NoUniqueBeanDefinitionException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationListener; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.event.SmartApplicationListener; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.SecurityAssertions; -import org.springframework.security.authentication.event.AuthenticationSuccessEvent; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurerTests.OAuth2LoginConfigCustomWithPostProcessor.SpyObjectPostProcessor; -import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.context.DelegatingApplicationListener; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; -import org.springframework.security.core.context.SecurityContextChangedListener; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.core.session.SessionDestroyedEvent; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; -import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry; -import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; -import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; -import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; -import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository; -import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.TestOidcIdTokens; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; -import org.springframework.security.oauth2.core.oidc.user.OidcUser; -import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority; -import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers; -import org.springframework.security.oauth2.core.user.DefaultOAuth2User; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.security.oauth2.core.user.OAuth2UserAuthority; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtDecoderFactory; -import org.springframework.security.oauth2.jwt.TestJwts; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.RedirectStrategy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.HttpStatusEntryPoint; -import org.springframework.security.web.context.HttpRequestResponseHolder; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.security.web.context.NullSecurityContextRepository; -import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.security.web.servlet.TestMockHttpServletRequests; -import org.springframework.security.web.session.HttpSessionDestroyedEvent; -import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.then; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.web.servlet.TestMockHttpServletRequests.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; - -/** - * Tests for {@link OAuth2LoginConfigurer}. - * - * @author Kazuki Shimizu - * @author Joe Grandja - * @since 5.0.1 - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2LoginConfigurerTests { - - // @formatter:off - private static final ClientRegistration GOOGLE_CLIENT_REGISTRATION = CommonOAuth2Provider.GOOGLE - .getBuilder("google") - .clientId("clientId") - .clientSecret("clientSecret") - .build(); - // @formatter:on - - // @formatter:off - private static final ClientRegistration GITHUB_CLIENT_REGISTRATION = CommonOAuth2Provider.GITHUB - .getBuilder("github") - .clientId("clientId") - .clientSecret("clientSecret") - .build(); - // @formatter:on - - // @formatter:off - private static final ClientRegistration CLIENT_CREDENTIALS_REGISTRATION = TestClientRegistrations.clientCredentials() - .build(); - // @formatter:on - - private ConfigurableApplicationContext context; - - @Autowired - private FilterChainProxy springSecurityFilterChain; - - @Autowired(required = false) - private AuthorizationRequestRepository authorizationRequestRepository; - - @Autowired(required = false) - SecurityContextRepository securityContextRepository; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired(required = false) - MockMvc mvc; - - private MockHttpServletRequest request; - - private MockHttpServletResponse response; - - private MockFilterChain filterChain; - - @BeforeEach - public void setup() { - this.request = TestMockHttpServletRequests.get("/login/oauth2/code/google").build(); - this.response = new MockHttpServletResponse(); - this.filterChain = new MockFilterChain(); - } - - @AfterEach - public void cleanup() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void oauth2Login() throws Exception { - // setup application context - loadConfig(OAuth2LoginConfig.class); - // setup authorization request - OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); - // setup authentication parameters - this.request.setParameter("code", "code123"); - this.request.setParameter("state", authorizationRequest.getState()); - // perform test - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - // assertions - Authentication authentication = this.securityContextRepository - .loadContext(new HttpRequestResponseHolder(this.request, this.response)) - .getAuthentication(); - SecurityAssertions.assertThat(authentication) - .hasAuthority("OAUTH2_USER") - .isInstanceOf(OAuth2UserAuthority.class); - } - - @Test - public void requestWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { - loadConfig(OAuth2LoginConfig.class, SecurityContextChangedListenerConfig.class); - OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); - this.request.setParameter("code", "code123"); - this.request.setParameter("state", authorizationRequest.getState()); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - Authentication authentication = this.securityContextRepository - .loadContext(new HttpRequestResponseHolder(this.request, this.response)) - .getAuthentication(); - SecurityAssertions.assertThat(authentication) - .hasAuthority("OAUTH2_USER") - .isInstanceOf(OAuth2UserAuthority.class); - SecurityContextHolderStrategy strategy = this.context.getBean(SecurityContextHolderStrategy.class); - verify(strategy, atLeastOnce()).getDeferredContext(); - SecurityContextChangedListener listener = this.context.getBean(SecurityContextChangedListener.class); - verify(listener).securityContextChanged(setAuthentication(OAuth2AuthenticationToken.class)); - } - - @Test - public void requestWhenOauth2LoginInLambdaThenAuthenticationContainsOauth2UserAuthority() throws Exception { - loadConfig(OAuth2LoginInLambdaConfig.class); - OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); - this.request.setParameter("code", "code123"); - this.request.setParameter("state", authorizationRequest.getState()); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - Authentication authentication = this.securityContextRepository - .loadContext(new HttpRequestResponseHolder(this.request, this.response)) - .getAuthentication(); - SecurityAssertions.assertThat(authentication) - .hasAuthority("OAUTH2_USER") - .isInstanceOf(OAuth2UserAuthority.class); - } - - // gh-6009 - @Test - public void oauth2LoginWhenSuccessThenAuthenticationSuccessEventPublished() throws Exception { - // setup application context - loadConfig(OAuth2LoginConfig.class); - // setup authorization request - OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); - // setup authentication parameters - this.request.setParameter("code", "code123"); - this.request.setParameter("state", authorizationRequest.getState()); - // perform test - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - // assertions - assertThat(OAuth2LoginConfig.EVENTS).isNotEmpty(); - assertThat(OAuth2LoginConfig.EVENTS).hasSize(1); - assertThat(OAuth2LoginConfig.EVENTS.get(0)).isInstanceOf(AuthenticationSuccessEvent.class); - } - - @Test - public void oauth2LoginCustomWithConfigurer() throws Exception { - // setup application context - loadConfig(OAuth2LoginConfigCustomWithConfigurer.class); - // setup authorization request - OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); - // setup authentication parameters - this.request.setParameter("code", "code123"); - this.request.setParameter("state", authorizationRequest.getState()); - // perform test - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - // assertions - Authentication authentication = this.securityContextRepository - .loadContext(new HttpRequestResponseHolder(this.request, this.response)) - .getAuthentication(); - SecurityAssertions.assertThat(authentication).hasAuthorities("OAUTH2_USER", "ROLE_OAUTH2_USER"); - } - - @Test - public void oauth2LoginCustomWithBeanRegistration() throws Exception { - // setup application context - loadConfig(OAuth2LoginConfigCustomWithBeanRegistration.class); - // setup authorization request - OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); - // setup authentication parameters - this.request.setParameter("code", "code123"); - this.request.setParameter("state", authorizationRequest.getState()); - // perform test - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - // assertions - Authentication authentication = this.securityContextRepository - .loadContext(new HttpRequestResponseHolder(this.request, this.response)) - .getAuthentication(); - SecurityAssertions.assertThat(authentication).hasAuthorities("OAUTH2_USER", "ROLE_OAUTH2_USER"); - } - - @Test - public void oauth2LoginCustomWithUserServiceBeanRegistration() throws Exception { - // setup application context - loadConfig(OAuth2LoginConfigCustomUserServiceBeanRegistration.class); - // setup authorization request - OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); - // setup authentication parameters - this.request.setParameter("code", "code123"); - this.request.setParameter("state", authorizationRequest.getState()); - // perform test - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - // assertions - Authentication authentication = this.securityContextRepository - .loadContext(new HttpRequestResponseHolder(this.request, this.response)) - .getAuthentication(); - SecurityAssertions.assertThat(authentication).hasAuthorities("OAUTH2_USER", "ROLE_OAUTH2_USER"); - } - - // gh-5488 - @Test - public void oauth2LoginConfigLoginProcessingUrl() throws Exception { - // setup application context - loadConfig(OAuth2LoginConfigLoginProcessingUrl.class); - // setup authorization request - OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); - this.request.setRequestURI("/login/oauth2/google"); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); - // setup authentication parameters - this.request.setParameter("code", "code123"); - this.request.setParameter("state", authorizationRequest.getState()); - // perform test - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - // assertions - Authentication authentication = this.securityContextRepository - .loadContext(new HttpRequestResponseHolder(this.request, this.response)) - .getAuthentication(); - SecurityAssertions.assertThat(authentication) - .hasAuthority("OAUTH2_USER") - .isInstanceOf(OAuth2UserAuthority.class); - } - - // gh-5521 - @Test - public void oauth2LoginWithCustomAuthorizationRequestParameters() throws Exception { - loadConfig(OAuth2LoginConfigCustomAuthorizationRequestResolver.class); - OAuth2AuthorizationRequestResolver resolver = this.context - .getBean(OAuth2LoginConfigCustomAuthorizationRequestResolver.class).resolver; - // @formatter:off - OAuth2AuthorizationRequest result = OAuth2AuthorizationRequest.authorizationCode() - .authorizationUri("https://accounts.google.com/authorize") - .clientId("client-id") - .state("adsfa") - .authorizationRequestUri( - "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1") - .build(); - // @formatter:on - given(resolver.resolve(any())).willReturn(result); - String requestUri = "/oauth2/authorization/google"; - this.request = TestMockHttpServletRequests.get(requestUri).build(); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).isEqualTo( - "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1"); - } - - @Test - public void oauth2LoginWithCustomAuthorizationRequestParametersAndResolverAsBean() throws Exception { - loadConfig(OAuth2LoginConfigCustomAuthorizationRequestResolverBean.class); - // @formatter:off - // @formatter:on - String requestUri = "/oauth2/authorization/google"; - this.request = TestMockHttpServletRequests.get(requestUri).build(); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).isEqualTo( - "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1"); - } - - @Test - public void requestWhenOauth2LoginWithCustomAuthorizationRequestParametersThenParametersInRedirectedUrl() - throws Exception { - loadConfig(OAuth2LoginConfigCustomAuthorizationRequestResolverInLambda.class); - OAuth2AuthorizationRequestResolver resolver = this.context - .getBean(OAuth2LoginConfigCustomAuthorizationRequestResolverInLambda.class).resolver; - // @formatter:off - OAuth2AuthorizationRequest result = OAuth2AuthorizationRequest.authorizationCode() - .authorizationUri("https://accounts.google.com/authorize") - .clientId("client-id") - .state("adsfa") - .authorizationRequestUri( - "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1") - .build(); - // @formatter:on - given(resolver.resolve(any())).willReturn(result); - String requestUri = "/oauth2/authorization/google"; - this.request = TestMockHttpServletRequests.get(requestUri).build(); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).isEqualTo( - "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1"); - } - - @Test - public void oauth2LoginWithAuthorizationRedirectStrategyThenCustomAuthorizationRedirectStrategyUsed() - throws Exception { - loadConfig(OAuth2LoginConfigCustomAuthorizationRedirectStrategy.class); - RedirectStrategy redirectStrategy = this.context - .getBean(OAuth2LoginConfigCustomAuthorizationRedirectStrategy.class).redirectStrategy; - String requestUri = "/oauth2/authorization/google"; - this.request = get(requestUri).build(); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - then(redirectStrategy).should().sendRedirect(any(), any(), anyString()); - } - - @Test - public void requestWhenOauth2LoginWithCustomAuthorizationRedirectStrategyThenCustomAuthorizationRedirectStrategyUsed() - throws Exception { - loadConfig(OAuth2LoginConfigCustomAuthorizationRedirectStrategyInLambda.class); - RedirectStrategy redirectStrategy = this.context - .getBean(OAuth2LoginConfigCustomAuthorizationRedirectStrategyInLambda.class).redirectStrategy; - String requestUri = "/oauth2/authorization/google"; - this.request = get(requestUri).build(); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - then(redirectStrategy).should().sendRedirect(any(), any(), anyString()); - } - - // gh-5347 - @Test - public void oauth2LoginWithOneClientConfiguredThenRedirectForAuthorization() throws Exception { - loadConfig(OAuth2LoginConfig.class); - String requestUri = "/"; - this.request = get(requestUri).build(); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("/oauth2/authorization/google"); - } - - // gh-6802 - @Test - public void oauth2LoginWithOneClientConfiguredAndFormLoginThenRedirectDefaultLoginPage() throws Exception { - loadConfig(OAuth2LoginConfigFormLogin.class); - String requestUri = "/"; - this.request = get(requestUri).build(); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("/login"); - } - - // gh-5347 - @Test - public void oauth2LoginWithOneClientConfiguredAndRequestFaviconNotAuthenticatedThenRedirectDefaultLoginPage() - throws Exception { - loadConfig(OAuth2LoginConfig.class); - String requestUri = "/favicon.ico"; - this.request = get(requestUri).build(); - this.request.addHeader(HttpHeaders.ACCEPT, new MediaType("image", "*").toString()); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("/login"); - } - - // gh-5347 - @Test - public void oauth2LoginWithMultipleClientsConfiguredThenRedirectDefaultLoginPage() throws Exception { - loadConfig(OAuth2LoginConfigMultipleClients.class); - String requestUri = "/"; - this.request = get(requestUri).build(); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("/login"); - } - - // gh-6812 - @Test - public void oauth2LoginWithOneClientConfiguredAndRequestXHRNotAuthenticatedThenDoesNotRedirectForAuthorization() - throws Exception { - loadConfig(OAuth2LoginConfig.class); - String requestUri = "/"; - this.request = get(requestUri).build(); - this.request.addHeader("X-Requested-With", "XMLHttpRequest"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).doesNotMatch("http://localhost/oauth2/authorization/google"); - } - - @Test - public void oauth2LoginWithHttpBasicOneClientConfiguredAndRequestXHRNotAuthenticatedThenUnauthorized() - throws Exception { - loadConfig(OAuth2LoginWithHttpBasicConfig.class); - String requestUri = "/"; - this.request = get(requestUri).build(); - this.request.addHeader("X-Requested-With", "XMLHttpRequest"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getStatus()).isEqualTo(401); - } - - @Test - public void oauth2LoginWithXHREntryPointOneClientConfiguredAndRequestXHRNotAuthenticatedThenUnauthorized() - throws Exception { - loadConfig(OAuth2LoginWithXHREntryPointConfig.class); - String requestUri = "/"; - this.request = get(requestUri).build(); - this.request.addHeader("X-Requested-With", "XMLHttpRequest"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getStatus()).isEqualTo(401); - } - - // gh-9457 - @Test - public void oauth2LoginWithOneAuthorizationCodeClientAndOtherClientsConfiguredThenRedirectForAuthorization() - throws Exception { - loadConfig(OAuth2LoginConfigAuthorizationCodeClientAndOtherClients.class); - String requestUri = "/"; - this.request = get(requestUri).build(); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("/oauth2/authorization/google"); - } - - @Test - public void oauth2LoginWithCustomLoginPageThenRedirectCustomLoginPage() throws Exception { - loadConfig(OAuth2LoginConfigCustomLoginPage.class); - String requestUri = "/"; - this.request = get(requestUri).build(); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("/custom-login"); - } - - @Test - public void requestWhenOauth2LoginWithCustomLoginPageInLambdaThenRedirectCustomLoginPage() throws Exception { - loadConfig(OAuth2LoginConfigCustomLoginPageInLambda.class); - String requestUri = "/"; - this.request = get(requestUri).build(); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - assertThat(this.response.getRedirectedUrl()).matches("/custom-login"); - } - - @Test - public void oidcLogin() throws Exception { - // setup application context - loadConfig(OAuth2LoginConfig.class, JwtDecoderFactoryConfig.class); - // setup authorization request - OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid"); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); - // setup authentication parameters - this.request.setParameter("code", "code123"); - this.request.setParameter("state", authorizationRequest.getState()); - // perform test - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - // assertions - Authentication authentication = this.securityContextRepository - .loadContext(new HttpRequestResponseHolder(this.request, this.response)) - .getAuthentication(); - SecurityAssertions.assertThat(authentication).hasAuthority("OIDC_USER").isInstanceOf(OidcUserAuthority.class); - } - - @Test - public void requestWhenOauth2LoginInLambdaAndOidcThenAuthenticationContainsOidcUserAuthority() throws Exception { - // setup application context - loadConfig(OAuth2LoginInLambdaConfig.class, JwtDecoderFactoryConfig.class); - // setup authorization request - OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid"); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); - // setup authentication parameters - this.request.setParameter("code", "code123"); - this.request.setParameter("state", authorizationRequest.getState()); - // perform test - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - // assertions - Authentication authentication = this.securityContextRepository - .loadContext(new HttpRequestResponseHolder(this.request, this.response)) - .getAuthentication(); - assertThat(authentication.getAuthorities()).hasSize(1); - SecurityAssertions.assertThat(authentication).hasAuthority("OIDC_USER").isInstanceOf(OidcUserAuthority.class); - } - - @Test - public void oidcLoginCustomWithConfigurer() throws Exception { - // setup application context - loadConfig(OAuth2LoginConfigCustomWithConfigurer.class, JwtDecoderFactoryConfig.class); - // setup authorization request - OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid"); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); - // setup authentication parameters - this.request.setParameter("code", "code123"); - this.request.setParameter("state", authorizationRequest.getState()); - // perform test - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - // assertions - Authentication authentication = this.securityContextRepository - .loadContext(new HttpRequestResponseHolder(this.request, this.response)) - .getAuthentication(); - SecurityAssertions.assertThat(authentication).hasAuthorities("OIDC_USER", "ROLE_OIDC_USER"); - } - - @Test - public void oidcLoginCustomWithBeanRegistration() throws Exception { - // setup application context - loadConfig(OAuth2LoginConfigCustomWithBeanRegistration.class, JwtDecoderFactoryConfig.class); - // setup authorization request - OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid"); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); - // setup authentication parameters - this.request.setParameter("code", "code123"); - this.request.setParameter("state", authorizationRequest.getState()); - // perform test - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - // assertions - Authentication authentication = this.securityContextRepository - .loadContext(new HttpRequestResponseHolder(this.request, this.response)) - .getAuthentication(); - SecurityAssertions.assertThat(authentication).hasAuthorities("OIDC_USER", "ROLE_OIDC_USER"); - } - - @Test - public void oidcLoginCustomWithNoUniqueJwtDecoderFactory() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> loadConfig(OAuth2LoginConfig.class, NoUniqueJwtDecoderFactoryConfig.class)) - .withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class) - .withMessageContaining("No qualifying bean of type " - + "'org.springframework.security.oauth2.jwt.JwtDecoderFactory' " - + "available: expected single matching bean but found 2: jwtDecoderFactory1,jwtDecoderFactory2"); - } - - @Test - public void logoutWhenUsingOidcLogoutHandlerThenRedirects() throws Exception { - this.spring.register(OAuth2LoginConfigWithOidcLogoutSuccessHandler.class).autowire(); - OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(TestOidcUsers.create(), - AuthorityUtils.NO_AUTHORITIES, "registration-id"); - this.mvc.perform(post("/logout").with(authentication(token)).with(csrf())) - .andExpect(redirectedUrl("https://logout?id_token_hint=id-token")); - } - - @Test - public void configureWhenOidcSessionRegistryThenUses() { - this.spring.register(OAuth2LoginWithOidcSessionRegistry.class).autowire(); - OidcSessionRegistry registry = this.spring.getContext().getBean(OidcSessionRegistry.class); - this.spring.getContext().publishEvent(new HttpSessionDestroyedEvent(this.request.getSession())); - verify(registry).removeSessionInformation(this.request.getSession().getId()); - } - - // gh-14558 - @Test - public void oauth2LoginWhenDefaultsThenNoOidcSessionRegistry() { - this.spring.register(OAuth2LoginConfig.class).autowire(); - DelegatingApplicationListener listener = this.spring.getContext().getBean(DelegatingApplicationListener.class); - List listeners = (List) ReflectionTestUtils - .getField(listener, "listeners"); - assertThat(listeners.stream() - .filter((l) -> l.supportsEventType(SessionDestroyedEvent.class)) - .collect(Collectors.toList())).isEmpty(); - } - - @Test - public void oidcLoginWhenOAuth2ClientBeansConfiguredThenNotShared() throws Exception { - this.spring.register(OAuth2LoginConfigWithOAuth2Client.class, JwtDecoderFactoryConfig.class).autowire(); - OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid"); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); - this.request.setParameter("code", "code123"); - this.request.setParameter("state", authorizationRequest.getState()); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - Authentication authentication = this.securityContextRepository - .loadContext(new HttpRequestResponseHolder(this.request, this.response)) - .getAuthentication(); - SecurityAssertions.assertThat(authentication).hasAuthority("OIDC_USER").isInstanceOf(OidcUserAuthority.class); - // Ensure shared objects set for OAuth2 Client are not used - ClientRegistrationRepository clientRegistrationRepository = this.spring.getContext() - .getBean(ClientRegistrationRepository.class); - OAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext() - .getBean(OAuth2AuthorizedClientRepository.class); - verifyNoInteractions(clientRegistrationRepository, authorizedClientRepository); - } - - // gh-17175 - @Test - public void oauth2LoginWhenAuthenticationProviderPostProcessorThenUses() throws Exception { - loadConfig(OAuth2LoginConfigCustomWithPostProcessor.class); - // setup authorization request - OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest(); - this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response); - // setup authentication parameters - this.request.setParameter("code", "code123"); - this.request.setParameter("state", authorizationRequest.getState()); - // perform test - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - // assertions - verify(this.context.getBean(SpyObjectPostProcessor.class).spy).authenticate(any()); - } - - // gh-16623 - @Test - public void oauth2LoginWithCustomSecurityContextRepository() { - assertThatNoException().isThrownBy(() -> loadConfig(OAuth2LoginConfigSecurityContextRepository.class)); - } - - private void loadConfig(Class... configs) { - AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); - applicationContext.register(configs); - applicationContext.refresh(); - applicationContext.getAutowireCapableBeanFactory().autowireBean(this); - this.context = applicationContext; - } - - private OAuth2AuthorizationRequest createOAuth2AuthorizationRequest(String... scopes) { - return this.createOAuth2AuthorizationRequest(GOOGLE_CLIENT_REGISTRATION, scopes); - } - - private OAuth2AuthorizationRequest createOAuth2AuthorizationRequest(ClientRegistration registration, - String... scopes) { - // @formatter:off - return OAuth2AuthorizationRequest.authorizationCode() - .authorizationUri(registration.getProviderDetails().getAuthorizationUri()) - .clientId(registration.getClientId()) - .state("state123") - .redirectUri("http://localhost") - .attributes(Collections.singletonMap(OAuth2ParameterNames.REGISTRATION_ID, - registration.getRegistrationId())) - .scope(scopes) - .build(); - // @formatter:on - } - - private static OAuth2AccessTokenResponseClient createOauth2AccessTokenResponseClient() { - return (request) -> { - Map additionalParameters = new HashMap<>(); - if (request.getAuthorizationExchange().getAuthorizationRequest().getScopes().contains("openid")) { - additionalParameters.put(OidcParameterNames.ID_TOKEN, "token123"); - } - return OAuth2AccessTokenResponse.withToken("accessToken123") - .tokenType(OAuth2AccessToken.TokenType.BEARER) - .additionalParameters(additionalParameters) - .build(); - }; - } - - private static OAuth2UserService createOauth2UserService() { - Map userAttributes = Collections.singletonMap("name", "spring"); - return (request) -> new DefaultOAuth2User(Collections.singleton(new OAuth2UserAuthority(userAttributes)), - userAttributes, "name"); - } - - private static OAuth2UserService createOidcUserService() { - OidcIdToken idToken = TestOidcIdTokens.idToken().build(); - return (request) -> new DefaultOidcUser(Collections.singleton(new OidcUserAuthority(idToken)), idToken); - } - - private static GrantedAuthoritiesMapper createGrantedAuthoritiesMapper() { - return (authorities) -> { - boolean isOidc = OidcUserAuthority.class.isInstance(authorities.iterator().next()); - List mappedAuthorities = new ArrayList<>(authorities); - mappedAuthorities.add(new SimpleGrantedAuthority(isOidc ? "ROLE_OIDC_USER" : "ROLE_OAUTH2_USER")); - return mappedAuthorities; - }; - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfig extends CommonSecurityFilterChainConfig - implements ApplicationListener { - - static List EVENTS = new ArrayList<>(); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((login) -> login - .clientRegistrationRepository( - new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION))); - // @formatter:on - return super.configureFilterChain(http); - } - - @Override - public void onApplicationEvent(AuthenticationSuccessEvent event) { - EVENTS.add(event); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigFormLogin extends CommonSecurityFilterChainConfig { - - private final InMemoryClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository( - GOOGLE_CLIENT_REGISTRATION); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((login) -> login - .clientRegistrationRepository(this.clientRegistrationRepository)) - .formLogin(withDefaults()); - // @formatter:on - return super.configureFilterChain(http); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginInLambdaConfig extends CommonLambdaSecurityFilterChainConfig - implements ApplicationListener { - - static List EVENTS = new ArrayList<>(); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((oauth2) -> oauth2 - .clientRegistrationRepository( - new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) - ); - // @formatter:on - return super.configureFilterChain(http); - } - - @Override - public void onApplicationEvent(AuthenticationSuccessEvent event) { - EVENTS.add(event); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigCustomWithConfigurer extends CommonSecurityFilterChainConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((login) -> login - .clientRegistrationRepository( - new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) - .userInfoEndpoint((info) -> info - .userAuthoritiesMapper(createGrantedAuthoritiesMapper()))); - // @formatter:on - return super.configureFilterChain(http); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigCustomWithBeanRegistration extends CommonSecurityFilterChainConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login(withDefaults()); - // @formatter:on - return super.configureFilterChain(http); - } - - @Bean - ClientRegistrationRepository clientRegistrationRepository() { - return new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION); - } - - @Bean - GrantedAuthoritiesMapper grantedAuthoritiesMapper() { - return createGrantedAuthoritiesMapper(); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigCustomUserServiceBeanRegistration { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .securityContext((context) -> context - .securityContextRepository(securityContextRepository())) - .oauth2Login((login) -> login - .tokenEndpoint((token) -> token - .accessTokenResponseClient(createOauth2AccessTokenResponseClient()))); - return http.build(); - // @formatter:on - } - - @Bean - ClientRegistrationRepository clientRegistrationRepository() { - return new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION); - } - - @Bean - GrantedAuthoritiesMapper grantedAuthoritiesMapper() { - return createGrantedAuthoritiesMapper(); - } - - @Bean - SecurityContextRepository securityContextRepository() { - return new HttpSessionSecurityContextRepository(); - } - - @Bean - HttpSessionOAuth2AuthorizationRequestRepository oauth2AuthorizationRequestRepository() { - return new HttpSessionOAuth2AuthorizationRequestRepository(); - } - - @Bean - OAuth2UserService oauth2UserService() { - return createOauth2UserService(); - } - - @Bean - OAuth2UserService oidcUserService() { - return createOidcUserService(); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigLoginProcessingUrl extends CommonSecurityFilterChainConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((login) -> login - .clientRegistrationRepository( - new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) - .loginProcessingUrl("/login/oauth2/*")); - // @formatter:on - return super.configureFilterChain(http); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigSecurityContextRepository extends CommonSecurityFilterChainConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((login) -> login - .clientRegistrationRepository( - new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) - .securityContextRepository(new NullSecurityContextRepository())); - // @formatter:on - return super.configureFilterChain(http); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigCustomAuthorizationRequestResolver extends CommonSecurityFilterChainConfig { - - private ClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository( - GOOGLE_CLIENT_REGISTRATION); - - OAuth2AuthorizationRequestResolver resolver = mock(OAuth2AuthorizationRequestResolver.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((login) -> login - .clientRegistrationRepository(this.clientRegistrationRepository) - .authorizationEndpoint((authorize) -> authorize - .authorizationRequestResolver(this.resolver))); - // @formatter:on - return super.configureFilterChain(http); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigCustomAuthorizationRequestResolverBean extends CommonSecurityFilterChainConfig { - - private ClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository( - GOOGLE_CLIENT_REGISTRATION); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((login) -> login - .clientRegistrationRepository(this.clientRegistrationRepository) - .authorizationEndpoint(Customizer.withDefaults())); - // @formatter:on - return super.configureFilterChain(http); - } - - @Bean - OAuth2AuthorizationRequestResolver resolver() { - OAuth2AuthorizationRequestResolver resolver = mock(OAuth2AuthorizationRequestResolver.class); - // @formatter:off - OAuth2AuthorizationRequest result = OAuth2AuthorizationRequest.authorizationCode() - .authorizationUri("https://accounts.google.com/authorize") - .clientId("client-id") - .state("adsfa") - .authorizationRequestUri( - "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1") - .build(); - given(resolver.resolve(any())).willReturn(result); - // @formatter:on - return resolver; - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigCustomAuthorizationRequestResolverInLambda - extends CommonLambdaSecurityFilterChainConfig { - - private ClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository( - GOOGLE_CLIENT_REGISTRATION); - - OAuth2AuthorizationRequestResolver resolver = mock(OAuth2AuthorizationRequestResolver.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((oauth2) -> oauth2 - .clientRegistrationRepository(this.clientRegistrationRepository) - .authorizationEndpoint((authorizationEndpoint) -> authorizationEndpoint - .authorizationRequestResolver(this.resolver) - ) - ); - // @formatter:on - return super.configureFilterChain(http); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigCustomAuthorizationRedirectStrategy extends CommonSecurityFilterChainConfig { - - private final ClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository( - GOOGLE_CLIENT_REGISTRATION); - - RedirectStrategy redirectStrategy = mock(RedirectStrategy.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((oauth2) -> oauth2 - .clientRegistrationRepository(this.clientRegistrationRepository) - .authorizationEndpoint((authorizationEndpoint) -> authorizationEndpoint - .authorizationRedirectStrategy(this.redirectStrategy) - ) - ); - // @formatter:on - return super.configureFilterChain(http); - } - - } - - @EnableWebSecurity - static class OAuth2LoginConfigCustomAuthorizationRedirectStrategyInLambda - extends CommonLambdaSecurityFilterChainConfig { - - private final ClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository( - GOOGLE_CLIENT_REGISTRATION); - - RedirectStrategy redirectStrategy = mock(RedirectStrategy.class); - - @Bean - SecurityFilterChain configureFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((oauth2) -> oauth2 - .clientRegistrationRepository(this.clientRegistrationRepository) - .authorizationEndpoint((authorizationEndpoint) -> authorizationEndpoint - .authorizationRedirectStrategy(this.redirectStrategy) - ) - ); - // @formatter:on - return super.configureFilterChain(http); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigMultipleClients extends CommonSecurityFilterChainConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((login) -> login - .clientRegistrationRepository( - new InMemoryClientRegistrationRepository( - GOOGLE_CLIENT_REGISTRATION, GITHUB_CLIENT_REGISTRATION))); - // @formatter:on - return super.configureFilterChain(http); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigAuthorizationCodeClientAndOtherClients extends CommonSecurityFilterChainConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((login) -> login - .clientRegistrationRepository( - new InMemoryClientRegistrationRepository( - GOOGLE_CLIENT_REGISTRATION, CLIENT_CREDENTIALS_REGISTRATION))); - // @formatter:on - return super.configureFilterChain(http); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigCustomLoginPage extends CommonSecurityFilterChainConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((login) -> login - .clientRegistrationRepository( - new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) - .loginPage("/custom-login")); - // @formatter:on - return super.configureFilterChain(http); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigCustomLoginPageInLambda extends CommonLambdaSecurityFilterChainConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((oauth2) -> oauth2 - .clientRegistrationRepository( - new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) - .loginPage("/custom-login") - ); - // @formatter:on - return super.configureFilterChain(http); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigWithOidcLogoutSuccessHandler extends CommonSecurityFilterChainConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .logout((logout) -> logout - .logoutSuccessHandler(oidcLogoutSuccessHandler())); - // @formatter:on - return super.configureFilterChain(http); - } - - @Bean - OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler() { - return new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository()); - } - - @Bean - ClientRegistrationRepository clientRegistrationRepository() { - Map providerMetadata = Collections.singletonMap("end_session_endpoint", "https://logout"); - return new InMemoryClientRegistrationRepository(TestClientRegistrations.clientRegistration() - .providerConfigurationMetadata(providerMetadata) - .build()); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginWithHttpBasicConfig extends CommonSecurityFilterChainConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((login) -> login - .clientRegistrationRepository( - new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION))) - .httpBasic(withDefaults()); - // @formatter:on - return super.configureFilterChain(http); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginWithOidcSessionRegistry { - - private final OidcSessionRegistry registry = mock(OidcSessionRegistry.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((oauth2) -> oauth2 - .clientRegistrationRepository( - new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) - .oidcSessionRegistry(this.registry) - ); - // @formatter:on - return http.build(); - } - - @Bean - OidcSessionRegistry oidcSessionRegistry() { - return this.registry; - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginWithXHREntryPointConfig extends CommonSecurityFilterChainConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((login) -> login - .clientRegistrationRepository( - new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION))) - .exceptionHandling((handling) -> handling - .defaultAuthenticationEntryPointFor( - new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), - new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"))); - // @formatter:on - return super.configureFilterChain(http); - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigWithOAuth2Client extends CommonLambdaSecurityFilterChainConfig { - - private final ClientRegistrationRepository clientRegistrationRepository = mock( - ClientRegistrationRepository.class); - - private final OAuth2AuthorizedClientRepository authorizedClientRepository = mock( - OAuth2AuthorizedClientRepository.class); - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((oauth2Login) -> oauth2Login - .clientRegistrationRepository( - new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION)) - .authorizedClientRepository(new HttpSessionOAuth2AuthorizedClientRepository()) - ) - .oauth2Client((oauth2Client) -> oauth2Client - .clientRegistrationRepository(this.clientRegistrationRepository) - .authorizedClientRepository(this.authorizedClientRepository) - ); - // @formatter:on - return super.configureFilterChain(http); - } - - @Bean - ClientRegistrationRepository clientRegistrationRepository() { - return this.clientRegistrationRepository; - } - - @Bean - OAuth2AuthorizedClientRepository authorizedClientRepository() { - return this.authorizedClientRepository; - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginConfigCustomWithPostProcessor { - - private final ClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository( - GOOGLE_CLIENT_REGISTRATION); - - private final ObjectPostProcessor postProcessor = new SpyObjectPostProcessor(); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2Login((oauth2Login) -> oauth2Login - .clientRegistrationRepository(this.clientRegistrationRepository) - .withObjectPostProcessor(this.postProcessor) - ); - // @formatter:on - return http.build(); - } - - @Bean - ObjectPostProcessor mockPostProcessor() { - return this.postProcessor; - } - - @Bean - HttpSessionOAuth2AuthorizationRequestRepository oauth2AuthorizationRequestRepository() { - return new HttpSessionOAuth2AuthorizationRequestRepository(); - } - - static class SpyObjectPostProcessor implements ObjectPostProcessor { - - AuthenticationProvider spy; - - @Override - public O postProcess(O object) { - O spy = Mockito.spy(object); - this.spy = spy; - return spy; - } - - } - - } - - private abstract static class CommonSecurityFilterChainConfig { - - SecurityFilterChain configureFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .securityContext((context) -> context - .securityContextRepository(securityContextRepository())) - .oauth2Login((login) -> login - .tokenEndpoint((token) -> token - .accessTokenResponseClient(createOauth2AccessTokenResponseClient())) - .userInfoEndpoint((info) -> info - .userService(createOauth2UserService()) - .oidcUserService(createOidcUserService()))); - // @formatter:on - return http.build(); - } - - @Bean - SecurityContextRepository securityContextRepository() { - return new HttpSessionSecurityContextRepository(); - } - - @Bean - HttpSessionOAuth2AuthorizationRequestRepository oauth2AuthorizationRequestRepository() { - return new HttpSessionOAuth2AuthorizationRequestRepository(); - } - - } - - private abstract static class CommonLambdaSecurityFilterChainConfig { - - SecurityFilterChain configureFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .securityContext((securityContext) -> securityContext - .securityContextRepository(securityContextRepository()) - ) - .oauth2Login((oauth2) -> oauth2 - .tokenEndpoint((tokenEndpoint) -> tokenEndpoint - .accessTokenResponseClient(createOauth2AccessTokenResponseClient()) - ) - .userInfoEndpoint((userInfoEndpoint) -> userInfoEndpoint - .userService(createOauth2UserService()) - .oidcUserService(createOidcUserService()) - ) - ); - // @formatter:on - return http.build(); - } - - @Bean - SecurityContextRepository securityContextRepository() { - return new HttpSessionSecurityContextRepository(); - } - - @Bean - HttpSessionOAuth2AuthorizationRequestRepository oauth2AuthorizationRequestRepository() { - return new HttpSessionOAuth2AuthorizationRequestRepository(); - } - - } - - @Configuration - static class JwtDecoderFactoryConfig { - - @Bean - JwtDecoderFactory jwtDecoderFactory() { - return (clientRegistration) -> getJwtDecoder(); - } - - private static JwtDecoder getJwtDecoder() { - Map claims = new HashMap<>(); - claims.put(IdTokenClaimNames.SUB, "sub123"); - claims.put(IdTokenClaimNames.ISS, "http://localhost/iss"); - claims.put(IdTokenClaimNames.AUD, Arrays.asList("clientId", "a", "u", "d")); - claims.put(IdTokenClaimNames.AZP, "clientId"); - Jwt jwt = TestJwts.jwt().claims((c) -> c.putAll(claims)).build(); - JwtDecoder jwtDecoder = mock(JwtDecoder.class); - given(jwtDecoder.decode(any())).willReturn(jwt); - return jwtDecoder; - } - - } - - @Configuration - static class NoUniqueJwtDecoderFactoryConfig { - - @Bean - JwtDecoderFactory jwtDecoderFactory1() { - return (clientRegistration) -> JwtDecoderFactoryConfig.getJwtDecoder(); - } - - @Bean - JwtDecoderFactory jwtDecoderFactory2() { - return (clientRegistration) -> JwtDecoderFactoryConfig.getJwtDecoder(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutHandlerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutHandlerTests.java deleted file mode 100644 index 911cc6f4427..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcBackChannelLogoutHandlerTests.java +++ /dev/null @@ -1,73 +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.annotation.web.configurers.oauth2.client; - -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.oauth2.client.oidc.authentication.logout.TestOidcLogoutTokens; -import org.springframework.security.oauth2.client.oidc.session.InMemoryOidcSessionRegistry; -import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; - -import static org.assertj.core.api.Assertions.assertThat; - -public class OidcBackChannelLogoutHandlerTests { - - private final OidcSessionRegistry sessionRegistry = new InMemoryOidcSessionRegistry(); - - private final OidcBackChannelLogoutAuthentication token = new OidcBackChannelLogoutAuthentication( - TestOidcLogoutTokens.withSubject("issuer", "subject").build(), - TestClientRegistrations.clientRegistration().build()); - - // gh-14553 - @Test - public void computeLogoutEndpointWhenDifferentHostnameThenLocalhost() { - OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler(this.sessionRegistry); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/back-channel/logout"); - logoutHandler.setLogoutUri("{baseScheme}://localhost{basePort}/logout"); - request.setServerName("host.docker.internal"); - request.setServerPort(8090); - String endpoint = logoutHandler.computeLogoutEndpoint(request, this.token); - assertThat(endpoint).startsWith("http://localhost:8090/logout"); - } - - @Test - public void computeLogoutEndpointWhenUsingBaseUrlTemplateThenServerName() { - OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler(this.sessionRegistry); - logoutHandler.setLogoutUri("{baseUrl}/logout"); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/back-channel/logout"); - request.setServerName("host.docker.internal"); - request.setServerPort(8090); - String endpoint = logoutHandler.computeLogoutEndpoint(request, this.token); - assertThat(endpoint).startsWith("http://host.docker.internal:8090/logout"); - } - - // gh-14609 - @Test - public void computeLogoutEndpointWhenLogoutUriThenUses() { - OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler(this.sessionRegistry); - logoutHandler.setLogoutUri("http://localhost:8090/logout"); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/back-channel/logout"); - request.setScheme("https"); - request.setServerName("server-one.com"); - request.setServerPort(80); - String endpoint = logoutHandler.computeLogoutEndpoint(request, this.token); - assertThat(endpoint).startsWith("http://localhost:8090/logout"); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcLogoutConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcLogoutConfigurerTests.java deleted file mode 100644 index 4d1c54743f8..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcLogoutConfigurerTests.java +++ /dev/null @@ -1,840 +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.annotation.web.configurers.oauth2.client; - -import java.io.IOException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.interfaces.RSAPublicKey; -import java.time.Instant; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jose.jwk.source.ImmutableJWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import com.nimbusds.oauth2.sdk.Scope; -import com.nimbusds.oauth2.sdk.token.BearerAccessToken; -import com.nimbusds.openid.connect.sdk.token.OIDCTokens; -import jakarta.annotation.PreDestroy; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpSession; -import okhttp3.mockwebserver.Dispatcher; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; -import org.htmlunit.util.UrlUtils; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.core.annotation.Order; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.mock.web.MockServletContext; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.oauth2.client.oidc.authentication.logout.LogoutTokenClaimNames; -import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken; -import org.springframework.security.oauth2.client.oidc.authentication.logout.TestOidcLogoutTokens; -import org.springframework.security.oauth2.client.oidc.session.InMemoryOidcSessionRegistry; -import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.TestOidcIdTokens; -import org.springframework.security.oauth2.core.oidc.user.OidcUser; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.hamcrest.Matchers.containsString; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.willThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link OidcLogoutConfigurer} - */ -@ExtendWith(SpringTestContextExtension.class) -public class OidcLogoutConfigurerTests { - - @Autowired - private MockMvc mvc; - - @Autowired(required = false) - private MockWebServer web; - - @Autowired - private ClientRegistration clientRegistration; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - void logoutWhenDefaultsThenRemotelyInvalidatesSessions() throws Exception { - this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire(); - String registrationId = this.clientRegistration.getRegistrationId(); - MockHttpSession session = login(); - String logoutToken = this.mvc.perform(get("/token/logout").session(session)) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(); - this.mvc - .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .param("logout_token", logoutToken)) - .andExpect(status().isOk()); - this.mvc.perform(get("/token/logout").session(session)).andExpect(status().isUnauthorized()); - } - - @Test - void logoutWhenInvalidLogoutTokenThenBadRequest() throws Exception { - this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire(); - this.mvc.perform(get("/token/logout")).andExpect(status().isUnauthorized()); - String registrationId = this.clientRegistration.getRegistrationId(); - MvcResult result = this.mvc.perform(get("/oauth2/authorization/" + registrationId)) - .andExpect(status().isFound()) - .andReturn(); - MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); - String redirectUrl = UrlUtils.decode(result.getResponse().getRedirectedUrl()); - String state = this.mvc - .perform(get(redirectUrl) - .with(httpBasic(this.clientRegistration.getClientId(), this.clientRegistration.getClientSecret()))) - .andReturn() - .getResponse() - .getContentAsString(); - result = this.mvc - .perform(get("/login/oauth2/code/" + registrationId).param("code", "code") - .param("state", state) - .session(session)) - .andExpect(status().isFound()) - .andReturn(); - session = (MockHttpSession) result.getRequest().getSession(); - this.mvc - .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .param("logout_token", "invalid")) - .andExpect(status().isBadRequest()); - this.mvc.perform(get("/token/logout").session(session)).andExpect(status().isOk()); - } - - @Test - void logoutWhenLogoutTokenSpecifiesOneSessionThenRemotelyInvalidatesOnlyThatSession() throws Exception { - this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire(); - String registrationId = this.clientRegistration.getRegistrationId(); - MockHttpSession one = login(); - MockHttpSession two = login(); - MockHttpSession three = login(); - String logoutToken = this.mvc.perform(get("/token/logout").session(one)) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(); - this.mvc - .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .param("logout_token", logoutToken)) - .andExpect(status().isOk()); - this.mvc.perform(get("/token/logout").session(one)).andExpect(status().isUnauthorized()); - this.mvc.perform(get("/token/logout").session(two)).andExpect(status().isOk()); - logoutToken = this.mvc.perform(get("/token/logout/all").session(three)) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(); - this.mvc - .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .param("logout_token", logoutToken)) - .andExpect(status().isOk()); - this.mvc.perform(get("/token/logout").session(two)).andExpect(status().isUnauthorized()); - this.mvc.perform(get("/token/logout").session(three)).andExpect(status().isUnauthorized()); - } - - @Test - void logoutWhenRemoteLogoutUriThenUses() throws Exception { - this.spring.register(WebServerConfig.class, OidcProviderConfig.class, LogoutUriConfig.class).autowire(); - String registrationId = this.clientRegistration.getRegistrationId(); - MockHttpSession one = login(); - String logoutToken = this.mvc.perform(get("/token/logout/all").session(one)) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(); - this.mvc - .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .param("logout_token", logoutToken)) - .andExpect(status().isBadRequest()) - .andExpect(content().string(containsString("partial_logout"))) - .andExpect(content().string(containsString("not all sessions were terminated"))); - this.mvc.perform(get("/token/logout").session(one)).andExpect(status().isOk()); - } - - @Test - void logoutWhenSelfRemoteLogoutUriThenUses() throws Exception { - this.spring.register(WebServerConfig.class, OidcProviderConfig.class, SelfLogoutUriConfig.class).autowire(); - String registrationId = this.clientRegistration.getRegistrationId(); - MockHttpSession session = login(); - String logoutToken = this.mvc.perform(get("/token/logout").session(session)) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(); - this.mvc - .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .param("logout_token", logoutToken)) - .andExpect(status().isOk()); - this.mvc.perform(get("/token/logout").session(session)).andExpect(status().isUnauthorized()); - } - - @Test - void logoutWhenDifferentCookieNameThenUses() throws Exception { - this.spring.register(OidcProviderConfig.class, CookieConfig.class).autowire(); - String registrationId = this.clientRegistration.getRegistrationId(); - MockHttpSession session = login(); - String logoutToken = this.mvc.perform(get("/token/logout").session(session)) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(); - this.mvc - .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .param("logout_token", logoutToken)) - .andExpect(status().isOk()); - this.mvc.perform(get("/token/logout").session(session)).andExpect(status().isUnauthorized()); - } - - @Test - void logoutWhenRemoteLogoutFailsThenReportsPartialLogout() throws Exception { - this.spring.register(WebServerConfig.class, OidcProviderConfig.class, WithBrokenLogoutConfig.class).autowire(); - LogoutHandler logoutHandler = this.spring.getContext().getBean(LogoutHandler.class); - willThrow(IllegalStateException.class).given(logoutHandler).logout(any(), any(), any()); - String registrationId = this.clientRegistration.getRegistrationId(); - MockHttpSession one = login(); - String logoutToken = this.mvc.perform(get("/token/logout/all").session(one)) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(); - this.mvc - .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .param("logout_token", logoutToken)) - .andExpect(status().isBadRequest()) - .andExpect(content().string(containsString("partial_logout"))); - this.mvc.perform(get("/token/logout").session(one)).andExpect(status().isOk()); - } - - @Test - void logoutWhenCustomComponentsThenUses() throws Exception { - this.spring.register(WebServerConfig.class, OidcProviderConfig.class, WithCustomComponentsConfig.class) - .autowire(); - String registrationId = this.clientRegistration.getRegistrationId(); - MockHttpSession session = login(); - String logoutToken = this.mvc.perform(get("/token/logout").session(session)) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(); - this.mvc - .perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .param("logout_token", logoutToken)) - .andExpect(status().isOk()); - this.mvc.perform(get("/token/logout").session(session)).andExpect(status().isUnauthorized()); - OidcSessionRegistry sessionRegistry = this.spring.getContext().getBean(OidcSessionRegistry.class); - verify(sessionRegistry).saveSessionInformation(any()); - verify(sessionRegistry).removeSessionInformation(any(OidcLogoutToken.class)); - } - - @Test - void logoutWhenProviderIssuerMissingThenThrowIllegalArgumentException() throws Exception { - this.spring.register(WebServerConfig.class, OidcProviderConfig.class, ProviderIssuerMissingConfig.class) - .autowire(); - String registrationId = this.clientRegistration.getRegistrationId(); - MockHttpSession session = login(); - String logoutToken = this.mvc.perform(get("/token/logout").session(session)) - .andExpect(status().isOk()) - .andReturn() - .getResponse() - .getContentAsString(); - assertThatIllegalArgumentException().isThrownBy( - () -> this.mvc.perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .param("logout_token", logoutToken))); - } - - private MockHttpSession login() throws Exception { - MockMvcDispatcher dispatcher = (MockMvcDispatcher) this.web.getDispatcher(); - this.mvc.perform(get("/token/logout")).andExpect(status().isUnauthorized()); - String registrationId = this.clientRegistration.getRegistrationId(); - MvcResult result = this.mvc.perform(get("/oauth2/authorization/" + registrationId)) - .andExpect(status().isFound()) - .andReturn(); - MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); - String redirectUrl = UrlUtils.decode(result.getResponse().getRedirectedUrl()); - String state = this.mvc - .perform(get(redirectUrl) - .with(httpBasic(this.clientRegistration.getClientId(), this.clientRegistration.getClientSecret()))) - .andReturn() - .getResponse() - .getContentAsString(); - result = this.mvc - .perform(get("/login/oauth2/code/" + registrationId).param("code", "code") - .param("state", state) - .session(session)) - .andExpect(status().isFound()) - .andReturn(); - session = (MockHttpSession) result.getRequest().getSession(); - dispatcher.registerSession(session); - return session; - } - - @Configuration - static class RegistrationConfig { - - @Autowired(required = false) - MockWebServer web; - - @Bean - ClientRegistration clientRegistration() { - if (this.web == null) { - return TestClientRegistrations.clientRegistration().build(); - } - String issuer = this.web.url("/").toString(); - return TestClientRegistrations.clientRegistration() - .issuerUri(issuer) - .jwkSetUri(issuer + "jwks") - .tokenUri(issuer + "token") - .userInfoUri(issuer + "user") - .scope("openid") - .build(); - } - - @Bean - ClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) { - return new InMemoryClientRegistrationRepository(clientRegistration); - } - - } - - @Configuration - @EnableWebSecurity - @Import(RegistrationConfig.class) - static class DefaultConfig { - - @Bean - @Order(1) - SecurityFilterChain filters(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .oauth2Login(Customizer.withDefaults()) - .oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults())); - // @formatter:on - - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @Import(RegistrationConfig.class) - static class LogoutUriConfig { - - @Bean - @Order(1) - SecurityFilterChain filters(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .oauth2Login(Customizer.withDefaults()) - .oidcLogout((oidc) -> oidc - .backChannel((backchannel) -> backchannel.logoutUri("http://localhost/wrong")) - ); - // @formatter:on - - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @Import(RegistrationConfig.class) - static class SelfLogoutUriConfig { - - @Bean - @Order(1) - SecurityFilterChain filters(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .oauth2Login(Customizer.withDefaults()) - .oidcLogout((oidc) -> oidc - .backChannel(Customizer.withDefaults()) - ); - // @formatter:on - - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @Import(RegistrationConfig.class) - static class CookieConfig { - - private final MockWebServer server = new MockWebServer(); - - @Bean - @Order(1) - SecurityFilterChain filters(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .oauth2Login(Customizer.withDefaults()) - .oidcLogout((oidc) -> oidc - .backChannel(Customizer.withDefaults()) - ); - // @formatter:on - - return http.build(); - } - - @Bean - OidcSessionRegistry sessionRegistry() { - return new InMemoryOidcSessionRegistry(); - } - - @Bean - OidcBackChannelLogoutHandler oidcLogoutHandler(OidcSessionRegistry sessionRegistry) { - OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler(sessionRegistry); - logoutHandler.setSessionCookieName("SESSION"); - return logoutHandler; - } - - @Bean - MockWebServer web(ObjectProvider mvc) { - MockMvcDispatcher dispatcher = new MockMvcDispatcher(mvc); - dispatcher.setAssertion((rr) -> { - String cookie = rr.getHeaders().get("Cookie"); - if (cookie == null) { - return; - } - assertThat(cookie).contains("SESSION").doesNotContain("JSESSIONID"); - }); - this.server.setDispatcher(dispatcher); - return this.server; - } - - @PreDestroy - void shutdown() throws IOException { - this.server.shutdown(); - } - - } - - @Configuration - @EnableWebSecurity - @Import(RegistrationConfig.class) - static class WithCustomComponentsConfig { - - OidcSessionRegistry sessionRegistry = spy(new InMemoryOidcSessionRegistry()); - - @Bean - @Order(1) - SecurityFilterChain filters(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .oauth2Login(Customizer.withDefaults()) - .oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults())); - // @formatter:on - - return http.build(); - } - - @Bean - OidcSessionRegistry sessionRegistry() { - return this.sessionRegistry; - } - - } - - @Configuration - @EnableWebSecurity - @Import(RegistrationConfig.class) - static class WithBrokenLogoutConfig { - - private final LogoutHandler logoutHandler = mock(LogoutHandler.class); - - @Bean - @Order(1) - SecurityFilterChain filters(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .logout((logout) -> logout.addLogoutHandler(this.logoutHandler)) - .oauth2Login(Customizer.withDefaults()) - .oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults())); - // @formatter:on - - return http.build(); - } - - @Bean - LogoutHandler logoutHandler() { - return this.logoutHandler; - } - - } - - @Configuration - static class ProviderIssuerMissingRegistrationConfig { - - @Autowired(required = false) - MockWebServer web; - - @Bean - ClientRegistration clientRegistration() { - if (this.web == null) { - return TestClientRegistrations.clientRegistration().issuerUri(null).build(); - } - String issuer = this.web.url("/").toString(); - return TestClientRegistrations.clientRegistration() - .issuerUri(null) - .jwkSetUri(issuer + "jwks") - .tokenUri(issuer + "token") - .userInfoUri(issuer + "user") - .scope("openid") - .build(); - } - - @Bean - ClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) { - return new InMemoryClientRegistrationRepository(clientRegistration); - } - - } - - @Configuration - @EnableWebSecurity - @Import(ProviderIssuerMissingRegistrationConfig.class) - static class ProviderIssuerMissingConfig { - - @Bean - @Order(1) - SecurityFilterChain filters(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .oauth2Login(Customizer.withDefaults()) - .oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults())); - // @formatter:on - - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - @RestController - static class OidcProviderConfig { - - private static final RSAKey key = key(); - - private static final JWKSource jwks = jwks(key); - - private static RSAKey key() { - try { - KeyPair pair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); - return new RSAKey.Builder((RSAPublicKey) pair.getPublic()).privateKey(pair.getPrivate()).build(); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - private static JWKSource jwks(RSAKey key) { - try { - return new ImmutableJWKSet<>(new JWKSet(key)); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - private final String username = "user"; - - private final JwtEncoder encoder = new NimbusJwtEncoder(jwks); - - private String nonce; - - @Autowired - ClientRegistration registration; - - @Autowired(required = false) - MockWebServer web; - - @Bean - @Order(0) - SecurityFilterChain authorizationServer(HttpSecurity http, ClientRegistration registration) throws Exception { - // @formatter:off - http - .securityMatcher("/jwks", "/login/oauth/authorize", "/nonce", "/token", "/token/logout", "/user") - .authorizeHttpRequests((authorize) -> authorize - .requestMatchers("/jwks").permitAll() - .anyRequest().authenticated() - ) - .httpBasic(Customizer.withDefaults()) - .oauth2ResourceServer((oauth2) -> oauth2 - .jwt((jwt) -> jwt.jwkSetUri(registration.getProviderDetails().getJwkSetUri())) - ); - // @formatter:off - - return http.build(); - } - - @Bean - UserDetailsService users(ClientRegistration registration) { - return new InMemoryUserDetailsManager(User.withUsername(registration.getClientId()) - .password("{noop}" + registration.getClientSecret()).authorities("APP").build()); - } - - @GetMapping("/login/oauth/authorize") - String nonce(@RequestParam("nonce") String nonce, @RequestParam("state") String state) { - this.nonce = nonce; - return state; - } - - @PostMapping("/token") - Map accessToken(HttpServletRequest request) { - HttpSession session = request.getSession(); - JwtEncoderParameters parameters = JwtEncoderParameters - .from(JwtClaimsSet.builder().id("id").subject(this.username) - .issuer(getIssuerUri()).issuedAt(Instant.now()) - .expiresAt(Instant.now().plusSeconds(86400)).claim("scope", "openid").build()); - String token = this.encoder.encode(parameters).getTokenValue(); - return new OIDCTokens(idToken(session.getId()), new BearerAccessToken(token, 86400, new Scope("openid")), null) - .toJSONObject(); - } - - String idToken(String sessionId) { - OidcIdToken token = TestOidcIdTokens.idToken().issuer(getIssuerUri()) - .subject(this.username).expiresAt(Instant.now().plusSeconds(86400)) - .audience(List.of(this.registration.getClientId())).nonce(this.nonce) - .claim(LogoutTokenClaimNames.SID, sessionId).build(); - JwtEncoderParameters parameters = JwtEncoderParameters - .from(JwtClaimsSet.builder().claims((claims) -> claims.putAll(token.getClaims())).build()); - return this.encoder.encode(parameters).getTokenValue(); - } - - private String getIssuerUri() { - if (this.web == null) { - return TestClientRegistrations.clientRegistration().build().getProviderDetails().getIssuerUri(); - } - return this.web.url("/").toString(); - } - - @GetMapping("/user") - Map userinfo() { - return Map.of("sub", this.username, "id", this.username); - } - - @GetMapping("/jwks") - String jwks() { - return new JWKSet(key).toString(); - } - - @GetMapping("/token/logout") - String logoutToken(@AuthenticationPrincipal OidcUser user) { - OidcLogoutToken token = TestOidcLogoutTokens.withUser(user) - .audience(List.of(this.registration.getClientId())).build(); - JwsHeader header = JwsHeader.with(SignatureAlgorithm.RS256).type("logout+jwt").build(); - JwtClaimsSet claims = JwtClaimsSet.builder().claims((c) -> c.putAll(token.getClaims())).build(); - JwtEncoderParameters parameters = JwtEncoderParameters.from(header, claims); - return this.encoder.encode(parameters).getTokenValue(); - } - - @GetMapping("/token/logout/all") - String logoutTokenAll(@AuthenticationPrincipal OidcUser user) { - OidcLogoutToken token = TestOidcLogoutTokens.withUser(user) - .audience(List.of(this.registration.getClientId())) - .claims((claims) -> claims.remove(LogoutTokenClaimNames.SID)).build(); - JwsHeader header = JwsHeader.with(SignatureAlgorithm.RS256).type("JWT").build(); - JwtClaimsSet claims = JwtClaimsSet.builder().claims((c) -> c.putAll(token.getClaims())).build(); - JwtEncoderParameters parameters = JwtEncoderParameters.from(header, claims); - return this.encoder.encode(parameters).getTokenValue(); - } - } - - @Configuration - static class WebServerConfig { - - private final MockWebServer server = new MockWebServer(); - - @Bean - MockWebServer web(ObjectProvider mvc) { - this.server.setDispatcher(new MockMvcDispatcher(mvc)); - return this.server; - } - - @PreDestroy - void shutdown() throws IOException { - this.server.shutdown(); - } - - } - - private static class MockMvcDispatcher extends Dispatcher { - - private final Map session = new ConcurrentHashMap<>(); - - private final ObjectProvider mvcProvider; - - private MockMvc mvc; - - private Consumer assertion = (rr) -> { }; - - MockMvcDispatcher(ObjectProvider mvc) { - this.mvcProvider = mvc; - } - - @Override - public MockResponse dispatch(RecordedRequest request) throws InterruptedException { - this.assertion.accept(request); - this.mvc = this.mvcProvider.getObject(); - String method = request.getMethod(); - String path = request.getPath(); - String csrf = request.getHeader("X-CSRF-TOKEN"); - MockHttpSession session = session(request); - MockHttpServletRequestBuilder builder; - if ("GET".equals(method)) { - builder = get(path); - } - else { - builder = post(path).content(request.getBody().readUtf8()); - if (csrf != null) { - builder.header("X-CSRF-TOKEN", csrf); - } - else { - builder.with(csrf()); - } - } - for (Map.Entry> header : request.getHeaders().toMultimap().entrySet()) { - builder.header(header.getKey(), header.getValue().iterator().next()); - } - try { - MockHttpServletResponse mvcResponse = this.mvc.perform(builder.session(session)).andReturn().getResponse(); - return toMockResponse(mvcResponse); - } - catch (Exception ex) { - MockResponse response = new MockResponse(); - response.setResponseCode(500); - return response; - } - } - - void registerSession(MockHttpSession session) { - this.session.put(session.getId(), session); - } - - void setAssertion(Consumer assertion) { - this.assertion = assertion; - } - - private MockHttpSession session(RecordedRequest request) { - String cookieHeaderValue = request.getHeader("Cookie"); - if (cookieHeaderValue == null) { - return new MockHttpSession(); - } - String[] cookies = cookieHeaderValue.split(";"); - for (String cookie : cookies) { - String[] parts = cookie.split("="); - if ("JSESSIONID".equals(parts[0])) { - return this.session.computeIfAbsent(parts[1], - (k) -> new MockHttpSession(new MockServletContext(), parts[1])); - } - if ("SESSION".equals(parts[0])) { - return this.session.computeIfAbsent(parts[1], - (k) -> new MockHttpSession(new MockServletContext(), parts[1])); - } - } - return new MockHttpSession(); - } - - private MockResponse toMockResponse(MockHttpServletResponse mvcResponse) { - MockResponse response = new MockResponse(); - response.setResponseCode(mvcResponse.getStatus()); - for (String name : mvcResponse.getHeaderNames()) { - response.addHeader(name, mvcResponse.getHeaderValue(name)); - } - response.setBody(getContentAsString(mvcResponse)); - return response; - } - - private String getContentAsString(MockHttpServletResponse response) { - try { - return response.getContentAsString(); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcUserRefreshedEventListenerConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcUserRefreshedEventListenerConfigurationTests.java deleted file mode 100644 index 06f33b58eb8..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcUserRefreshedEventListenerConfigurationTests.java +++ /dev/null @@ -1,512 +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.annotation.web.configurers.oauth2.client; - -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest; -import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; -import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; -import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.core.oidc.StandardClaimNames; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; -import org.springframework.security.oauth2.core.oidc.user.OidcUser; -import org.springframework.security.oauth2.core.user.DefaultOAuth2User; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtDecoderFactory; -import org.springframework.security.oauth2.jwt.TestJwts; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.springframework.security.web.servlet.TestMockHttpServletRequests.get; - -/** - * Tests for {@link OidcUserRefreshedEventListener} with {@link OAuth2LoginConfigurer}. - * - * @author Steve Riesenberg - */ -public class OidcUserRefreshedEventListenerConfigurationTests { - - // @formatter:off - private static final ClientRegistration GOOGLE_CLIENT_REGISTRATION = CommonOAuth2Provider.GOOGLE - .getBuilder("google") - .clientId("clientId") - .clientSecret("clientSecret") - .build(); - // @formatter:on - - // @formatter:off - private static final ClientRegistration GITHUB_CLIENT_REGISTRATION = CommonOAuth2Provider.GITHUB - .getBuilder("github") - .clientId("clientId") - .clientSecret("clientSecret") - .build(); - // @formatter:on - - private static final String SUBJECT = "surfer-dude"; - - private static final String ACCESS_TOKEN_VALUE = "hang-ten"; - - private static final String REFRESH_TOKEN_VALUE = "surfs-up"; - - private static final String ID_TOKEN_VALUE = "beach-break"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private SecurityContextRepository securityContextRepository; - - @Autowired - private OAuth2AuthorizedClientRepository authorizedClientRepository; - - @Autowired - private OAuth2AccessTokenResponseClient refreshTokenAccessTokenResponseClient; - - @Autowired - private JwtDecoder jwtDecoder; - - @Autowired - private OidcUserService oidcUserService; - - @Autowired - private OAuth2AuthorizedClientManager authorizedClientManager; - - private MockHttpServletRequest request; - - private MockHttpServletResponse response; - - @BeforeEach - public void setUp() { - this.request = get("/").build(); - this.response = new MockHttpServletResponse(); - RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(this.request, this.response)); - } - - @AfterEach - public void cleanUp() { - SecurityContextHolder.clearContext(); - RequestContextHolder.resetRequestAttributes(); - } - - @Test - public void authorizeWhenAccessTokenResponseMissingOpenidScopeThenOidcUserNotRefreshed() { - this.spring.register(OAuth2LoginWithOAuth2ClientConfig.class).autowire(); - - OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(); - OAuth2AccessTokenResponse accessTokenResponse = createAccessTokenResponse(); - given(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(Authentication.class), - any(HttpServletRequest.class))) - .willReturn(authorizedClient); - given(this.refreshTokenAccessTokenResponseClient.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) - .willReturn(accessTokenResponse); - - OAuth2AuthenticationToken authentication = createAuthenticationToken(GOOGLE_CLIENT_REGISTRATION, - createOidcUser()); - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(GOOGLE_CLIENT_REGISTRATION.getRegistrationId()) - .principal(authentication) - .build(); - OAuth2AuthorizedClient refreshedAuthorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - assertThat(refreshedAuthorizedClient).isNotNull(); - verifyNoInteractions(this.securityContextRepository, this.jwtDecoder, this.oidcUserService); - } - - @Test - public void authorizeWhenAccessTokenResponseMissingIdTokenThenOidcUserNotRefreshed() { - this.spring.register(OAuth2LoginWithOAuth2ClientConfig.class).autowire(); - - OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.oidcAccessTokenResponse() - .build(); - given(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(Authentication.class), - any(HttpServletRequest.class))) - .willReturn(authorizedClient); - given(this.refreshTokenAccessTokenResponseClient.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) - .willReturn(accessTokenResponse); - - OAuth2AuthenticationToken authentication = createAuthenticationToken(GOOGLE_CLIENT_REGISTRATION, - createOidcUser()); - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(GOOGLE_CLIENT_REGISTRATION.getRegistrationId()) - .principal(authentication) - .build(); - OAuth2AuthorizedClient refreshedAuthorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - assertThat(refreshedAuthorizedClient).isNotNull(); - verifyNoInteractions(this.securityContextRepository, this.jwtDecoder, this.oidcUserService); - } - - @Test - public void authorizeWhenAuthenticationIsNotOAuth2ThenOidcUserNotRefreshed() { - this.spring.register(OAuth2LoginWithOAuth2ClientConfig.class).autowire(); - - OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(); - OAuth2AccessTokenResponse accessTokenResponse = createAccessTokenResponse(OidcScopes.OPENID); - given(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(Authentication.class), - any(HttpServletRequest.class))) - .willReturn(authorizedClient); - given(this.refreshTokenAccessTokenResponseClient.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) - .willReturn(accessTokenResponse); - - TestingAuthenticationToken authentication = new TestingAuthenticationToken(SUBJECT, null); - SecurityContextImpl securityContext = new SecurityContextImpl(authentication); - SecurityContextHolder.setContext(securityContext); - - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(GOOGLE_CLIENT_REGISTRATION.getRegistrationId()) - .principal(authentication) - .build(); - OAuth2AuthorizedClient refreshedAuthorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - assertThat(refreshedAuthorizedClient).isNotNull(); - verifyNoInteractions(this.securityContextRepository, this.jwtDecoder, this.oidcUserService); - } - - @Test - public void authorizeWhenAuthenticationIsCustomThenOidcUserNotRefreshed() { - this.spring.register(OAuth2LoginWithOAuth2ClientConfig.class).autowire(); - - OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(); - OAuth2AccessTokenResponse accessTokenResponse = createAccessTokenResponse(OidcScopes.OPENID); - given(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(Authentication.class), - any(HttpServletRequest.class))) - .willReturn(authorizedClient); - given(this.refreshTokenAccessTokenResponseClient.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) - .willReturn(accessTokenResponse); - - OidcUser oidcUser = createOidcUser(); - OAuth2AuthenticationToken authentication = new CustomOAuth2AuthenticationToken(oidcUser, - oidcUser.getAuthorities(), GOOGLE_CLIENT_REGISTRATION.getRegistrationId()); - SecurityContextImpl securityContext = new SecurityContextImpl(authentication); - SecurityContextHolder.setContext(securityContext); - - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(GOOGLE_CLIENT_REGISTRATION.getRegistrationId()) - .principal(authentication) - .build(); - OAuth2AuthorizedClient refreshedAuthorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - assertThat(refreshedAuthorizedClient).isNotNull(); - verifyNoInteractions(this.securityContextRepository, this.jwtDecoder, this.oidcUserService); - } - - @Test - public void authorizeWhenPrincipalIsOAuth2UserThenOidcUserNotRefreshed() { - this.spring.register(OAuth2LoginWithOAuth2ClientConfig.class).autowire(); - - OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(); - OAuth2AccessTokenResponse accessTokenResponse = createAccessTokenResponse(OidcScopes.OPENID); - given(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(Authentication.class), - any(HttpServletRequest.class))) - .willReturn(authorizedClient); - given(this.refreshTokenAccessTokenResponseClient.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) - .willReturn(accessTokenResponse); - - Map attributes = Map.of(StandardClaimNames.SUB, SUBJECT); - OAuth2User oauth2User = new DefaultOAuth2User(AuthorityUtils.createAuthorityList("OAUTH2_USER"), attributes, - StandardClaimNames.SUB); - OAuth2AuthenticationToken authentication = new OAuth2AuthenticationToken(oauth2User, - oauth2User.getAuthorities(), GOOGLE_CLIENT_REGISTRATION.getRegistrationId()); - SecurityContextImpl securityContext = new SecurityContextImpl(authentication); - SecurityContextHolder.setContext(securityContext); - - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(GOOGLE_CLIENT_REGISTRATION.getRegistrationId()) - .principal(authentication) - .build(); - OAuth2AuthorizedClient refreshedAuthorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - assertThat(refreshedAuthorizedClient).isNotNull(); - verifyNoInteractions(this.securityContextRepository, this.jwtDecoder, this.oidcUserService); - } - - @Test - public void authorizeWhenAuthenticationClientRegistrationIdDoesNotMatchThenOidcUserNotRefreshed() { - this.spring.register(OAuth2LoginWithOAuth2ClientConfig.class).autowire(); - - OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(); - OAuth2AccessTokenResponse accessTokenResponse = createAccessTokenResponse(OidcScopes.OPENID); - given(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(Authentication.class), - any(HttpServletRequest.class))) - .willReturn(authorizedClient); - given(this.refreshTokenAccessTokenResponseClient.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) - .willReturn(accessTokenResponse); - - OAuth2AuthenticationToken authentication = createAuthenticationToken(GITHUB_CLIENT_REGISTRATION, - createOidcUser()); - SecurityContextImpl securityContext = new SecurityContextImpl(authentication); - SecurityContextHolder.setContext(securityContext); - - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(GOOGLE_CLIENT_REGISTRATION.getRegistrationId()) - .principal(authentication) - .build(); - OAuth2AuthorizedClient refreshedAuthorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - assertThat(refreshedAuthorizedClient).isNotNull(); - verifyNoInteractions(this.securityContextRepository, this.jwtDecoder, this.oidcUserService); - } - - @Test - public void authorizeWhenAccessTokenResponseIncludesIdTokenThenOidcUserRefreshed() { - this.spring.register(OAuth2LoginWithOAuth2ClientConfig.class).autowire(); - - OAuth2AuthorizedClient authorizedClient = createAuthorizedClient(); - OAuth2AccessTokenResponse accessTokenResponse = createAccessTokenResponse(OidcScopes.OPENID); - Jwt jwt = createJwt().build(); - given(this.authorizedClientRepository.loadAuthorizedClient(anyString(), any(Authentication.class), - any(HttpServletRequest.class))) - .willReturn(authorizedClient); - given(this.refreshTokenAccessTokenResponseClient.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) - .willReturn(accessTokenResponse); - given(this.jwtDecoder.decode(anyString())).willReturn(jwt); - given(this.oidcUserService.loadUser(any(OidcUserRequest.class))).willReturn(createOidcUser()); - - OAuth2AuthenticationToken authentication = createAuthenticationToken(GOOGLE_CLIENT_REGISTRATION, - createOidcUser()); - SecurityContextImpl securityContext = new SecurityContextImpl(authentication); - SecurityContextHolder.setContext(securityContext); - - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(GOOGLE_CLIENT_REGISTRATION.getRegistrationId()) - .principal(authentication) - .build(); - OAuth2AuthorizedClient refreshedAuthorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - assertThat(refreshedAuthorizedClient).isNotNull(); - assertThat(refreshedAuthorizedClient).isNotSameAs(authorizedClient); - assertThat(refreshedAuthorizedClient.getClientRegistration()).isEqualTo(GOOGLE_CLIENT_REGISTRATION); - assertThat(refreshedAuthorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); - assertThat(refreshedAuthorizedClient.getRefreshToken()).isEqualTo(accessTokenResponse.getRefreshToken()); - - ArgumentCaptor refreshTokenGrantRequestCaptor = ArgumentCaptor - .forClass(OAuth2RefreshTokenGrantRequest.class); - ArgumentCaptor userRequestCaptor = ArgumentCaptor.forClass(OidcUserRequest.class); - ArgumentCaptor securityContextCaptor = ArgumentCaptor.forClass(SecurityContext.class); - verify(this.authorizedClientRepository).loadAuthorizedClient(GOOGLE_CLIENT_REGISTRATION.getRegistrationId(), - authentication, this.request); - verify(this.authorizedClientRepository).saveAuthorizedClient(refreshedAuthorizedClient, authentication, - this.request, this.response); - verify(this.refreshTokenAccessTokenResponseClient).getTokenResponse(refreshTokenGrantRequestCaptor.capture()); - verify(this.jwtDecoder).decode(jwt.getTokenValue()); - verify(this.oidcUserService).loadUser(userRequestCaptor.capture()); - verify(this.securityContextRepository).saveContext(securityContextCaptor.capture(), eq(this.request), - eq(this.response)); - verifyNoMoreInteractions(this.authorizedClientRepository, this.jwtDecoder, this.oidcUserService, - this.securityContextRepository); - - OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = refreshTokenGrantRequestCaptor.getValue(); - assertThat(refreshTokenGrantRequest.getClientRegistration()) - .isEqualTo(authorizedClient.getClientRegistration()); - assertThat(refreshTokenGrantRequest.getRefreshToken()).isEqualTo(authorizedClient.getRefreshToken()); - assertThat(refreshTokenGrantRequest.getAccessToken()).isEqualTo(authorizedClient.getAccessToken()); - - OidcUserRequest userRequest = userRequestCaptor.getValue(); - assertThat(userRequest.getClientRegistration()).isEqualTo(GOOGLE_CLIENT_REGISTRATION); - assertThat(userRequest.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); - assertThat(userRequest.getIdToken().getTokenValue()).isEqualTo(jwt.getTokenValue()); - - SecurityContext refreshedSecurityContext = securityContextCaptor.getValue(); - assertThat(refreshedSecurityContext).isNotNull(); - assertThat(refreshedSecurityContext).isNotSameAs(securityContext); - assertThat(refreshedSecurityContext).isSameAs(SecurityContextHolder.getContext()); - assertThat(refreshedSecurityContext.getAuthentication()).isInstanceOf(OAuth2AuthenticationToken.class); - assertThat(refreshedSecurityContext.getAuthentication()).isNotSameAs(authentication); - assertThat(refreshedSecurityContext.getAuthentication().getPrincipal()).isInstanceOf(OidcUser.class); - assertThat(refreshedSecurityContext.getAuthentication().getPrincipal()) - .isNotSameAs(authentication.getPrincipal()); - } - - private OAuth2AuthorizedClient createAuthorizedClient() { - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(30, ChronoUnit.SECONDS); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, ACCESS_TOKEN_VALUE, - issuedAt, expiresAt, Set.of(OidcScopes.OPENID)); - OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(REFRESH_TOKEN_VALUE, issuedAt); - - return new OAuth2AuthorizedClient(GOOGLE_CLIENT_REGISTRATION, SUBJECT, accessToken, refreshToken); - } - - private OAuth2AccessTokenResponse createAccessTokenResponse(String... scope) { - Set scopes = Set.of(scope); - Map additionalParameters = new HashMap<>(); - if (scopes.contains(OidcScopes.OPENID)) { - additionalParameters.put(OidcParameterNames.ID_TOKEN, ID_TOKEN_VALUE); - } - - return OAuth2AccessTokenResponse.withToken(ACCESS_TOKEN_VALUE) - .tokenType(OAuth2AccessToken.TokenType.BEARER) - .scopes(scopes) - .refreshToken(REFRESH_TOKEN_VALUE) - .expiresIn(60L) - .additionalParameters(additionalParameters) - .build(); - } - - private static Jwt.Builder createJwt() { - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(1, ChronoUnit.MINUTES); - return TestJwts.jwt() - .issuer("https://surf.school") - .subject(SUBJECT) - .tokenValue(ID_TOKEN_VALUE) - .issuedAt(issuedAt) - .expiresAt(expiresAt) - .audience(List.of("audience1", "audience2")); - } - - private static OidcUser createOidcUser() { - Instant issuedAt = Instant.now().minus(30, ChronoUnit.SECONDS); - Instant expiresAt = issuedAt.plus(5, ChronoUnit.MINUTES); - Map claims = new HashMap<>(); - claims.put(IdTokenClaimNames.ISS, "https://surf.school"); - claims.put(IdTokenClaimNames.SUB, SUBJECT); - claims.put(IdTokenClaimNames.IAT, issuedAt); - claims.put(IdTokenClaimNames.EXP, expiresAt); - claims.put(IdTokenClaimNames.AUD, List.of("audience1", "audience2")); - claims.put(IdTokenClaimNames.AUTH_TIME, issuedAt); - claims.put(IdTokenClaimNames.NONCE, "nonce"); - OidcIdToken idToken = new OidcIdToken(ID_TOKEN_VALUE, issuedAt, expiresAt, claims); - - return new DefaultOidcUser(AuthorityUtils.createAuthorityList("OIDC_USER"), idToken); - } - - private OAuth2AuthenticationToken createAuthenticationToken(ClientRegistration clientRegistration, - OidcUser oidcUser) { - return new OAuth2AuthenticationToken(oidcUser, oidcUser.getAuthorities(), - clientRegistration.getRegistrationId()); - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginWithOAuth2ClientConfig { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .securityContext((securityContext) -> securityContext - .securityContextRepository(this.securityContextRepository()) - ) - .oauth2Login(Customizer.withDefaults()) - .oauth2Client(Customizer.withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - SecurityContextRepository securityContextRepository() { - return mock(SecurityContextRepository.class); - } - - @Bean - ClientRegistrationRepository clientRegistrationRepository() { - return mock(ClientRegistrationRepository.class); - } - - @Bean - OAuth2AuthorizedClientRepository authorizedClientRepository() { - return mock(OAuth2AuthorizedClientRepository.class); - } - - @Bean - @SuppressWarnings("unchecked") - OAuth2AccessTokenResponseClient refreshTokenAccessTokenResponseClient() { - return mock(OAuth2AccessTokenResponseClient.class); - } - - @Bean - JwtDecoder jwtDecoder() { - return mock(JwtDecoder.class); - } - - @Bean - JwtDecoderFactory jwtDecoderFactory() { - return (clientRegistration) -> jwtDecoder(); - } - - @Bean - OidcUserService oidcUserService() { - return mock(OidcUserService.class); - } - - } - - private static final class CustomOAuth2AuthenticationToken extends OAuth2AuthenticationToken { - - CustomOAuth2AuthenticationToken(OAuth2User principal, Collection authorities, - String authorizedClientRegistrationId) { - super(principal, authorities, authorizedClientRegistrationId); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcUserRefreshedEventListenerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcUserRefreshedEventListenerTests.java deleted file mode 100644 index e731437bad0..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcUserRefreshedEventListenerTests.java +++ /dev/null @@ -1,135 +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.annotation.web.configurers.oauth2.client; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.springframework.security.oauth2.client.oidc.authentication.event.OidcUserRefreshedEvent; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; -import org.springframework.security.oauth2.core.oidc.user.OidcUser; -import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers; -import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.springframework.security.web.servlet.TestMockHttpServletRequests.get; - -/** - * Tests for {@link OidcUserRefreshedEventListener}. - * - * @author Steve Riesenberg - */ -public class OidcUserRefreshedEventListenerTests { - - private OidcUserRefreshedEventListener eventListener; - - private SecurityContextRepository securityContextRepository; - - private MockHttpServletRequest request; - - private MockHttpServletResponse response; - - @BeforeEach - public void setUp() { - this.securityContextRepository = mock(SecurityContextRepository.class); - this.eventListener = new OidcUserRefreshedEventListener(); - this.eventListener.setSecurityContextRepository(this.securityContextRepository); - - this.request = get("/").build(); - this.response = new MockHttpServletResponse(); - } - - @AfterEach - public void cleanUp() { - SecurityContextHolder.clearContext(); - RequestContextHolder.resetRequestAttributes(); - } - - @Test - public void setSecurityContextHolderStrategyWhenNullThenThrowsIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.eventListener.setSecurityContextHolderStrategy(null)) - .withMessage("securityContextHolderStrategy cannot be null"); - } - - @Test - public void setSecurityContextRepositoryWhenNullThenThrowsIllegalArgumentException() { - assertThatIllegalArgumentException().isThrownBy(() -> this.eventListener.setSecurityContextRepository(null)) - .withMessage("securityContextRepository cannot be null"); - } - - @Test - public void onApplicationEventWhenRequestAttributesSetThenSecurityContextSaved() { - RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(this.request, this.response)); - - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.oidcAccessTokenResponse() - .build(); - OidcUser oldOidcUser = TestOidcUsers.create(); - OidcUser newOidcUser = TestOidcUsers.create(); - OAuth2AuthenticationToken authentication = new OAuth2AuthenticationToken(newOidcUser, - newOidcUser.getAuthorities(), "test"); - OidcUserRefreshedEvent event = new OidcUserRefreshedEvent(accessTokenResponse, oldOidcUser, newOidcUser, - authentication); - this.eventListener.onApplicationEvent(event); - - ArgumentCaptor securityContextCaptor = ArgumentCaptor.forClass(SecurityContext.class); - verify(this.securityContextRepository).saveContext(securityContextCaptor.capture(), eq(this.request), - eq(this.response)); - verifyNoMoreInteractions(this.securityContextRepository); - - SecurityContext securityContext = securityContextCaptor.getValue(); - assertThat(securityContext).isNotNull(); - assertThat(securityContext).isSameAs(SecurityContextHolder.getContext()); - assertThat(securityContext.getAuthentication()).isSameAs(authentication); - } - - @Test - public void onApplicationEventWhenRequestAttributesNotSetThenSecurityContextNotSaved() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.oidcAccessTokenResponse() - .build(); - OidcUser oldOidcUser = TestOidcUsers.create(); - OidcUser newOidcUser = TestOidcUsers.create(); - OAuth2AuthenticationToken authentication = new OAuth2AuthenticationToken(newOidcUser, - newOidcUser.getAuthorities(), "test"); - OidcUserRefreshedEvent event = new OidcUserRefreshedEvent(accessTokenResponse, oldOidcUser, newOidcUser, - authentication); - OidcUserRefreshedEventListener eventListener = new OidcUserRefreshedEventListener(); - eventListener.setSecurityContextRepository(this.securityContextRepository); - eventListener.onApplicationEvent(event); - verifyNoInteractions(this.securityContextRepository); - - SecurityContext securityContext = SecurityContextHolder.getContext(); - assertThat(securityContext).isNotNull(); - assertThat(securityContext.getAuthentication()).isSameAs(authentication); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/AuthorizationServerContextFilterTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/AuthorizationServerContextFilterTests.java deleted file mode 100644 index 0a3a9305de4..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/AuthorizationServerContextFilterTests.java +++ /dev/null @@ -1,138 +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.annotation.web.configurers.oauth2.server.authorization; - -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; - -import jakarta.servlet.FilterChain; -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link AuthorizationServerContextFilter}. - * - * @author Joe Grandja - */ -class AuthorizationServerContextFilterTests { - - private static final String SCHEME = "https"; - - private static final String HOST = "example.com"; - - private static final int PORT = 8443; - - private static final String DEFAULT_ISSUER = SCHEME + "://" + HOST + ":" + PORT; - - private AuthorizationServerContextFilter filter; - - @Test - void doFilterWhenDefaultEndpointsThenIssuerResolved() throws Exception { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build(); - this.filter = new AuthorizationServerContextFilter(authorizationServerSettings); - - String issuerPath = "/issuer1"; - String issuerWithPath = DEFAULT_ISSUER.concat(issuerPath); - Set endpointUris = getEndpointUris(authorizationServerSettings); - - for (String endpointUri : endpointUris) { - assertResolvedIssuer(issuerPath.concat(endpointUri), issuerWithPath); - } - } - - @Test - void doFilterWhenCustomEndpointsThenIssuerResolved() throws Exception { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder() - .authorizationEndpoint("/oauth2/v1/authorize") - .deviceAuthorizationEndpoint("/oauth2/v1/device_authorization") - .deviceVerificationEndpoint("/oauth2/v1/device_verification") - .tokenEndpoint("/oauth2/v1/token") - .jwkSetEndpoint("/oauth2/v1/jwks") - .tokenRevocationEndpoint("/oauth2/v1/revoke") - .tokenIntrospectionEndpoint("/oauth2/v1/introspect") - .oidcClientRegistrationEndpoint("/connect/v1/register") - .oidcUserInfoEndpoint("/v1/userinfo") - .oidcLogoutEndpoint("/connect/v1/logout") - .build(); - this.filter = new AuthorizationServerContextFilter(authorizationServerSettings); - - String issuerPath = "/issuer2"; - String issuerWithPath = DEFAULT_ISSUER.concat(issuerPath); - Set endpointUris = getEndpointUris(authorizationServerSettings); - - for (String endpointUri : endpointUris) { - assertResolvedIssuer(issuerPath.concat(endpointUri), issuerWithPath); - } - } - - @Test - void doFilterWhenIssuerHasMultiplePathsThenIssuerResolved() throws Exception { - AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().build(); - this.filter = new AuthorizationServerContextFilter(authorizationServerSettings); - - String issuerPath = "/path1/path2/issuer3"; - String issuerWithPath = DEFAULT_ISSUER.concat(issuerPath); - Set endpointUris = getEndpointUris(authorizationServerSettings); - - for (String endpointUri : endpointUris) { - assertResolvedIssuer(issuerPath.concat(endpointUri), issuerWithPath); - } - } - - private void assertResolvedIssuer(String requestUri, String expectedIssuer) throws Exception { - MockHttpServletRequest request = createRequest(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - - AtomicReference resolvedIssuer = new AtomicReference<>(); - FilterChain filterChain = (req, resp) -> resolvedIssuer - .set(AuthorizationServerContextHolder.getContext().getIssuer()); - - this.filter.doFilter(request, response, filterChain); - - assertThat(resolvedIssuer.get()).isEqualTo(expectedIssuer); - } - - private static Set getEndpointUris(AuthorizationServerSettings authorizationServerSettings) { - Set endpointUris = new HashSet<>(); - endpointUris.add("/.well-known/oauth-authorization-server"); - endpointUris.add("/.well-known/openid-configuration"); - for (Map.Entry setting : authorizationServerSettings.getSettings().entrySet()) { - if (setting.getKey().endsWith("-endpoint")) { - endpointUris.add((String) setting.getValue()); - } - } - return endpointUris; - } - - private static MockHttpServletRequest createRequest(String requestUri) { - MockHttpServletRequest request = new MockHttpServletRequest(); - request.setRequestURI(requestUri); - request.setScheme(SCHEME); - request.setServerName(HOST); - request.setServerPort(PORT); - return request; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/DefaultOAuth2TokenCustomizersTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/DefaultOAuth2TokenCustomizersTests.java deleted file mode 100644 index 3c948210fd8..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/DefaultOAuth2TokenCustomizersTests.java +++ /dev/null @@ -1,243 +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.annotation.web.configurers.oauth2.server.authorization; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.JwtClaimNames; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeActor; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; -import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.util.TestX509Certificates; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; - -/** - * Tests for {@link DefaultOAuth2TokenCustomizers}. - * - * @author Steve Riesenberg - * @author Joe Grandja - */ -class DefaultOAuth2TokenCustomizersTests { - - private static final String ISSUER_1 = "issuer-1"; - - private static final String ISSUER_2 = "issuer-2"; - - private JwsHeader.Builder jwsHeaderBuilder; - - private JwtClaimsSet.Builder jwtClaimsBuilder; - - @BeforeEach - void setUp() { - this.jwsHeaderBuilder = JwsHeader.with(SignatureAlgorithm.RS256); - this.jwtClaimsBuilder = JwtClaimsSet.builder().issuer(ISSUER_1); - } - - @Test - void customizeWhenTokenTypeIsRefreshTokenThenNoClaimsAdded() { - // @formatter:off - JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) - .tokenType(OAuth2TokenType.REFRESH_TOKEN) - .build(); - // @formatter:on - DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); - JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); - assertThat(jwtClaimsSet.getClaims()).containsOnly(entry(JwtClaimNames.ISS, ISSUER_1)); - } - - @Test - void customizeWhenAuthorizationGrantIsNullThenNoClaimsAdded() { - // @formatter:off - JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .build(); - // @formatter:on - DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); - JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); - assertThat(jwtClaimsSet.getClaims()).containsOnly(entry(JwtClaimNames.ISS, ISSUER_1)); - } - - @Test - void customizeWhenTokenExchangeGrantAndResourcesThenNoClaimsAdded() { - OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock( - OAuth2TokenExchangeAuthenticationToken.class); - given(tokenExchangeAuthentication.getResources()).willReturn(Set.of("resource1", "resource2")); - // @formatter:off - JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .authorizationGrant(tokenExchangeAuthentication) - .build(); - // @formatter:on - DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); - JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); - // We do not populate claims (e.g. `aud`) based on the resource parameter - assertThat(jwtClaimsSet.getClaims()).containsOnly(entry(JwtClaimNames.ISS, ISSUER_1)); - } - - @Test - void customizeWhenTokenExchangeGrantAndAudiencesThenNoClaimsAdded() { - OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock( - OAuth2TokenExchangeAuthenticationToken.class); - given(tokenExchangeAuthentication.getAudiences()).willReturn(Set.of("audience1", "audience2")); - // @formatter:off - JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .authorizationGrant(tokenExchangeAuthentication) - .build(); - // @formatter:on - DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); - JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); - // NOTE: We do not populate claims (e.g. `aud`) based on the audience parameter - assertThat(jwtClaimsSet.getClaims()).containsOnly(entry(JwtClaimNames.ISS, ISSUER_1)); - } - - @Test - void customizeWhenTokenExchangeGrantAndDelegationThenActClaimAdded() { - OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = mock( - OAuth2TokenExchangeAuthenticationToken.class); - given(tokenExchangeAuthentication.getAudiences()).willReturn(Collections.emptySet()); - - Authentication subject = new TestingAuthenticationToken("subject", null); - OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor( - Map.of(JwtClaimNames.ISS, ISSUER_1, JwtClaimNames.SUB, "actor1")); - OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor( - Map.of(JwtClaimNames.ISS, ISSUER_2, JwtClaimNames.SUB, "actor2")); - OAuth2TokenExchangeCompositeAuthenticationToken principal = new OAuth2TokenExchangeCompositeAuthenticationToken( - subject, List.of(actor1, actor2)); - - // @formatter:off - JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .principal(principal) - .authorizationGrant(tokenExchangeAuthentication) - .build(); - // @formatter:on - DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); - JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); - assertThat(jwtClaimsSet.getClaims()).isNotEmpty(); - assertThat(jwtClaimsSet.getClaims()).hasSize(2); - assertThat(jwtClaimsSet.getClaims().get("act")).isNotNull(); - @SuppressWarnings("unchecked") - Map actClaim1 = (Map) jwtClaimsSet.getClaims().get("act"); - assertThat(actClaim1.get(JwtClaimNames.ISS)).isEqualTo(ISSUER_1); - assertThat(actClaim1.get(JwtClaimNames.SUB)).isEqualTo("actor1"); - @SuppressWarnings("unchecked") - Map actClaim2 = (Map) actClaim1.get("act"); - assertThat(actClaim2.get(JwtClaimNames.ISS)).isEqualTo(ISSUER_2); - assertThat(actClaim2.get(JwtClaimNames.SUB)).isEqualTo("actor2"); - } - - @Test - void customizeWhenPKIX509ClientCertificateAndCertificateBoundAccessTokensThenX5tClaimAdded() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH) - .clientSettings( - ClientSettings.builder() - .x509CertificateSubjectDN(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE[0].getSubjectX500Principal().getName()) - .build() - ) - .tokenSettings( - TokenSettings.builder() - .x509CertificateBoundAccessTokens(true) - .build() - ) - .build(); - // @formatter:on - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.TLS_CLIENT_AUTH, TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE); - OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = new OAuth2ClientCredentialsAuthenticationToken( - clientPrincipal, null, null); - // @formatter:off - JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .registeredClient(registeredClient) - .authorizationGrant(clientCredentialsAuthentication) - .build(); - // @formatter:on - DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); - JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); - assertThat(jwtClaimsSet.getClaims()).isNotEmpty(); - assertThat(jwtClaimsSet.getClaims()).hasSize(2); - Map cnfClaim = jwtClaimsSet.getClaim("cnf"); - assertThat(cnfClaim).isNotEmpty(); - assertThat(cnfClaim.get("x5t#S256")).isNotNull(); - } - - @Test - void customizeWhenSelfSignedX509ClientCertificateAndCertificateBoundAccessTokensThenX5tClaimAdded() { - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientAuthenticationMethod(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH) - .clientSettings( - ClientSettings.builder() - .jwkSetUrl("https://client.example.com/jwks") - .build() - ) - .tokenSettings( - TokenSettings.builder() - .x509CertificateBoundAccessTokens(true) - .build() - ) - .build(); - // @formatter:on - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH, - TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE); - OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = new OAuth2ClientCredentialsAuthenticationToken( - clientPrincipal, null, null); - // @formatter:off - JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder) - .tokenType(OAuth2TokenType.ACCESS_TOKEN) - .registeredClient(registeredClient) - .authorizationGrant(clientCredentialsAuthentication) - .build(); - // @formatter:on - DefaultOAuth2TokenCustomizers.jwtCustomizer().customize(tokenContext); - JwtClaimsSet jwtClaimsSet = this.jwtClaimsBuilder.build(); - assertThat(jwtClaimsSet.getClaims()).isNotEmpty(); - assertThat(jwtClaimsSet.getClaims()).hasSize(2); - Map cnfClaim = jwtClaimsSet.getClaim("cnf"); - assertThat(cnfClaim).isNotEmpty(); - assertThat(cnfClaim.get("x5t#S256")).isNotNull(); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/JwkSetTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/JwkSetTests.java deleted file mode 100644 index 291ec87adf1..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/JwkSetTests.java +++ /dev/null @@ -1,176 +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.annotation.web.configurers.oauth2.server.authorization; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.test.web.servlet.MockMvc; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the JWK Set endpoint. - * - * @author Florian Berthe - */ -@ExtendWith(SpringTestContextExtension.class) -public class JwkSetTests { - - private static final String DEFAULT_JWK_SET_ENDPOINT_URI = "/oauth2/jwks"; - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private AuthorizationServerSettings authorizationServerSettings; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenJwkSetThenReturnKeys() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - assertJwkSetRequestThenReturnKeys(DEFAULT_JWK_SET_ENDPOINT_URI); - } - - @Test - public void requestWhenJwkSetCustomEndpointThenReturnKeys() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomEndpoints.class).autowire(); - - assertJwkSetRequestThenReturnKeys(this.authorizationServerSettings.getJwkSetEndpoint()); - } - - @Test - public void requestWhenJwkSetRequestIncludesIssuerPathThenReturnKeys() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomEndpoints.class).autowire(); - - String issuer = "https://example.com:8443/issuer1"; - assertJwkSetRequestThenReturnKeys(issuer.concat(this.authorizationServerSettings.getJwkSetEndpoint())); - } - - private void assertJwkSetRequestThenReturnKeys(String jwkSetEndpointUri) throws Exception { - this.mvc.perform(get(jwkSetEndpointUri)) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.keys").isNotEmpty()) - .andExpect(jsonPath("$.keys").isArray()); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); - } - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - return new JdbcRegisteredClientRepository(jdbcOperations); - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationCustomEndpoints extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder() - .jwkSetEndpoint("/test/jwks") - .multipleIssuersAllowed(true) - .build(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java deleted file mode 100644 index 7b485487d8b..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java +++ /dev/null @@ -1,1588 +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.annotation.web.configurers.oauth2.server.authorization; - -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.security.Principal; -import java.text.MessageFormat; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Arrays; -import java.util.Base64; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.function.Consumer; - -import com.jayway.jsonpath.JsonPath; -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.assertj.core.matcher.AssertionMatcher; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -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.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.lang.Nullable; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.crypto.keygen.Base64StringKeyGenerator; -import org.springframework.security.crypto.keygen.StringKeyGenerator; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationContext; -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.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.token.JwtGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -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.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.containsString; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the OAuth 2.0 Authorization Code Grant. - * - * @author Joe Grandja - * @author Daniel Garnier-Moiroux - * @author Dmitriy Dubson - * @author Steve Riesenberg - * @author Greg Li - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2AuthorizationCodeGrantTests { - - private static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize"; - - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - // See RFC 7636: Appendix B. Example for the S256 code_challenge_method - // https://tools.ietf.org/html/rfc7636#appendix-B - private static final String S256_CODE_VERIFIER = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; - - private static final String S256_CODE_CHALLENGE = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"; - - private static final String AUTHORITIES_CLAIM = "authorities"; - - private static final String STATE_URL_UNENCODED = "awrD0fCnEcTUPFgmyy2SU89HZNcnAJ60ZW6l39YI0KyVjmIZ+004pwm9j55li7BoydXYysH4enZMF21Q"; - - private static final String STATE_URL_ENCODED = "awrD0fCnEcTUPFgmyy2SU89HZNcnAJ60ZW6l39YI0KyVjmIZ%2B004pwm9j55li7BoydXYysH4enZMF21Q"; - - private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); - - private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE); - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - private static NimbusJwtEncoder jwtEncoder; - - private static NimbusJwtEncoder dPoPProofJwtEncoder; - - private static AuthorizationServerSettings authorizationServerSettings; - - private static HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - private static AuthenticationConverter authorizationRequestConverter; - - private static Consumer> authorizationRequestConvertersConsumer; - - private static AuthenticationProvider authorizationRequestAuthenticationProvider; - - private static Consumer> authorizationRequestAuthenticationProvidersConsumer; - - private static AuthenticationSuccessHandler authorizationResponseHandler; - - private static AuthenticationFailureHandler authorizationErrorResponseHandler; - - private static SecurityContextRepository securityContextRepository; - - private static String consentPage = "/oauth2/consent"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private OAuth2AuthorizationService authorizationService; - - @Autowired - private JwtDecoder jwtDecoder; - - @Autowired(required = false) - private OAuth2TokenGenerator tokenGenerator; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - jwtEncoder = new NimbusJwtEncoder(jwkSource); - JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); - JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); - dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); - authorizationServerSettings = AuthorizationServerSettings.builder() - .authorizationEndpoint("/test/authorize") - .tokenEndpoint("/test/token") - .build(); - authorizationRequestConverter = mock(AuthenticationConverter.class); - authorizationRequestConvertersConsumer = mock(Consumer.class); - authorizationRequestAuthenticationProvider = mock(AuthenticationProvider.class); - authorizationRequestAuthenticationProvidersConsumer = mock(Consumer.class); - authorizationResponseHandler = mock(AuthenticationSuccessHandler.class); - authorizationErrorResponseHandler = mock(AuthenticationFailureHandler.class); - securityContextRepository = spy(new HttpSessionSecurityContextRepository()); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - } - - @BeforeEach - public void setup() { - reset(securityContextRepository); - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_authorization_consent"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenAuthorizationRequestNotAuthenticatedThenUnauthorized() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .queryParams(getAuthorizationRequestParameters(registeredClient))) - .andExpect(status().isUnauthorized()) - .andReturn(); - } - - @Test - public void requestWhenRegisteredClientMissingThenBadRequest() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - - this.mvc - .perform( - get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).params(getAuthorizationRequestParameters(registeredClient))) - .andExpect(status().isBadRequest()) - .andReturn(); - } - - @Test - public void requestWhenAuthorizationRequestAuthenticatedThenRedirectToClient() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - assertAuthorizationRequestRedirectsToClient(DEFAULT_AUTHORIZATION_ENDPOINT_URI); - } - - @Test - public void requestWhenAuthorizationRequestCustomEndpointThenRedirectToClient() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomEndpoints.class).autowire(); - - assertAuthorizationRequestRedirectsToClient(authorizationServerSettings.getAuthorizationEndpoint()); - } - - private void assertAuthorizationRequestRedirectsToClient(String authorizationEndpointUri) throws Exception { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().redirectUris((redirectUris) -> { - redirectUris.clear(); - redirectUris.add("https://example.com/callback-1?param=encoded%20parameter%20value"); // gh-1011 - }).build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - MvcResult mvcResult = this.mvc - .perform(get(authorizationEndpointUri).queryParams(authorizationRequestParameters).with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String redirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - String code = extractParameterFromRedirectUri(redirectedUrl, "code"); - assertThat(redirectedUrl).isEqualTo(redirectUri + "&code=" + code + "&state=" + STATE_URL_ENCODED); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorization).isNotNull(); - assertThat(authorization.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - } - - @Test - public void requestWhenTokenRequestValidThenReturnAccessTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = createAuthorization(registeredClient); - this.authorizationService.save(authorization); - - OAuth2AccessTokenResponse accessTokenResponse = assertTokenRequestReturnsAccessTokenResponse(registeredClient, - authorization, DEFAULT_TOKEN_ENDPOINT_URI); - - // Assert user authorities was propagated as claim in JWT - Jwt jwt = this.jwtDecoder.decode(accessTokenResponse.getAccessToken().getTokenValue()); - List authoritiesClaim = jwt.getClaim(AUTHORITIES_CLAIM); - Authentication principal = authorization.getAttribute(Principal.class.getName()); - Set userAuthorities = new HashSet<>(); - for (GrantedAuthority authority : principal.getAuthorities()) { - userAuthorities.add(authority.getAuthority()); - } - - assertThat(authoritiesClaim).containsExactlyInAnyOrderElementsOf(userAuthorities); - } - - @Test - public void requestWhenTokenRequestCustomEndpointThenReturnAccessTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomEndpoints.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = createAuthorization(registeredClient); - this.authorizationService.save(authorization); - - assertTokenRequestReturnsAccessTokenResponse(registeredClient, authorization, - authorizationServerSettings.getTokenEndpoint()); - } - - private OAuth2AccessTokenResponse assertTokenRequestReturnsAccessTokenResponse(RegisteredClient registeredClient, - OAuth2Authorization authorization, String tokenEndpointUri) throws Exception { - MvcResult mvcResult = this.mvc - .perform(post(tokenEndpointUri).params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andReturn(); - - OAuth2Authorization accessTokenAuthorization = this.authorizationService.findById(authorization.getId()); - assertThat(accessTokenAuthorization).isNotNull(); - assertThat(accessTokenAuthorization.getAccessToken()).isNotNull(); - assertThat(accessTokenAuthorization.getRefreshToken()).isNotNull(); - - OAuth2Authorization.Token authorizationCodeToken = accessTokenAuthorization - .getToken(OAuth2AuthorizationCode.class); - assertThat(authorizationCodeToken).isNotNull(); - assertThat(authorizationCodeToken.getMetadata().get(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME)) - .isEqualTo(true); - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - return accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); - } - - @Test - public void requestWhenPublicClientWithPkceThenReturnAccessTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); - this.registeredClientRepository.save(registeredClient); - - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .queryParams(getAuthorizationRequestParameters(registeredClient)) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl).matches("https://example.com\\?code=.{15,}&state=" + STATE_URL_ENCODED); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorizationCodeAuthorization).isNotNull(); - assertThat(authorizationCodeAuthorization.getAuthorizationGrantType()) - .isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").doesNotExist()) - .andExpect(jsonPath("$.scope").isNotEmpty()); - - OAuth2Authorization accessTokenAuthorization = this.authorizationService - .findById(authorizationCodeAuthorization.getId()); - assertThat(accessTokenAuthorization).isNotNull(); - assertThat(accessTokenAuthorization.getAccessToken()).isNotNull(); - - OAuth2Authorization.Token authorizationCodeToken = accessTokenAuthorization - .getToken(OAuth2AuthorizationCode.class); - assertThat(authorizationCodeToken).isNotNull(); - assertThat(authorizationCodeToken.getMetadata().get(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME)) - .isEqualTo(true); - } - - // gh-1430 - @Test - public void requestWhenPublicClientWithPkceAndCustomRefreshTokenGeneratorThenReturnRefreshToken() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithCustomRefreshTokenGenerator.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient() - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .build(); - this.registeredClientRepository.save(registeredClient); - - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .queryParams(getAuthorizationRequestParameters(registeredClient)) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl).matches("https://example.com\\?code=.{15,}&state=" + STATE_URL_ENCODED); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorizationCodeAuthorization).isNotNull(); - assertThat(authorizationCodeAuthorization.getAuthorizationGrantType()) - .isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").isNotEmpty()); - - OAuth2Authorization authorization = this.authorizationService.findById(authorizationCodeAuthorization.getId()); - assertThat(authorization).isNotNull(); - assertThat(authorization.getAccessToken()).isNotNull(); - assertThat(authorization.getRefreshToken()).isNotNull(); - - OAuth2Authorization.Token authorizationCodeToken = authorization - .getToken(OAuth2AuthorizationCode.class); - assertThat(authorizationCodeToken).isNotNull(); - assertThat(authorizationCodeToken.getMetadata().get(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME)) - .isEqualTo(true); - } - - // gh-1680 - @Test - public void requestWhenPublicClientWithPkceAndEmptyCodeThenBadRequest() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap tokenRequestParameters = new LinkedMultiValueMap<>(); - tokenRequestParameters.set(OAuth2ParameterNames.GRANT_TYPE, - AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - tokenRequestParameters.set(OAuth2ParameterNames.CODE, ""); - tokenRequestParameters.set(OAuth2ParameterNames.REDIRECT_URI, - registeredClient.getRedirectUris().iterator().next()); - tokenRequestParameters.set(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(tokenRequestParameters) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())) - .andExpect(status().isBadRequest()); - } - - @Test - public void requestWhenConfidentialClientWithPkceAndMissingCodeVerifierThenBadRequest() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=" + STATE_URL_ENCODED); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorizationCodeAuthorization).isNotNull(); - assertThat(authorizationCodeAuthorization.getAuthorizationGrantType()) - .isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - - MultiValueMap tokenRequestParameters = getTokenRequestParameters(registeredClient, - authorizationCodeAuthorization); - tokenRequestParameters.remove(PkceParameterNames.CODE_VERIFIER); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(tokenRequestParameters) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(status().isBadRequest()); - } - - // gh-1011 - @Test - public void requestWhenConfidentialClientWithPkceAndMissingCodeChallengeThenErrorResponseEncoded() - throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - String redirectUri = "https://example.com/callback-1?param=encoded%20parameter%20value"; - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().redirectUris((redirectUris) -> { - redirectUris.clear(); - redirectUris.add(redirectUri); - }).build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - authorizationRequestParameters.remove(PkceParameterNames.CODE_CHALLENGE); - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String expectedRedirectUri = redirectUri + "&" + "error=invalid_request&" + "error_description=" - + UriUtils.encode("OAuth 2.0 Parameter: code_challenge", StandardCharsets.UTF_8) + "&" + "error_uri=" - + UriUtils.encode("https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1", StandardCharsets.UTF_8) - + "&" + "state=" + STATE_URL_ENCODED; - assertThat(redirectedUrl).isEqualTo(expectedRedirectUri); - } - - @Test - public void requestWhenConfidentialClientWithPkceAndMissingCodeChallengeButCodeVerifierProvidedThenBadRequest() - throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientSettings(ClientSettings.builder().requireProofKey(false).build()) - .build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - authorizationRequestParameters.remove(PkceParameterNames.CODE_CHALLENGE); - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=" + STATE_URL_ENCODED); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - assertThat(authorizationCodeAuthorization).isNotNull(); - assertThat(authorizationCodeAuthorization.getAuthorizationGrantType()) - .isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(status().isBadRequest()); - } - - @Test - public void requestWhenCustomTokenGeneratorThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithTokenGenerator.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = createAuthorization(registeredClient); - this.authorizationService.save(authorization); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(status().isOk()); - - verify(this.tokenGenerator, times(2)).generate(any()); - } - - @Test - public void requestWhenRequiresConsentThenDisplaysConsentPage() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.add("message.read"); - scopes.add("message.write"); - }).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build(); - this.registeredClientRepository.save(registeredClient); - - String consentPage = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .queryParams(getAuthorizationRequestParameters(registeredClient)) - .with(user("user"))) - .andExpect(status().is2xxSuccessful()) - .andReturn() - .getResponse() - .getContentAsString(); - - assertThat(consentPage).contains("Consent required"); - assertThat(consentPage).contains(scopeCheckbox("message.read")); - assertThat(consentPage).contains(scopeCheckbox("message.write")); - } - - @Test - public void requestWhenConsentRequestThenReturnAccessTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.add("message.read"); - scopes.add("message.write"); - }).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient) - .principalName("user") - .build(); - Map additionalParameters = new HashMap<>(); - additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE); - additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationRequest updatedAuthorizationRequest = OAuth2AuthorizationRequest.from(authorizationRequest) - .state(STATE_URL_UNENCODED) - .additionalParameters(additionalParameters) - .build(); - authorization = OAuth2Authorization.from(authorization) - .attribute(OAuth2AuthorizationRequest.class.getName(), updatedAuthorizationRequest) - .build(); - this.authorizationService.save(authorization); - - MvcResult mvcResult = this.mvc - .perform(post(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .param(OAuth2ParameterNames.SCOPE, "message.read") - .param(OAuth2ParameterNames.SCOPE, "message.write") - .param(OAuth2ParameterNames.STATE, authorization.getAttribute(OAuth2ParameterNames.STATE)) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl) - .matches(authorizationRequest.getRedirectUri() + "\\?code=.{15,}&state=" + STATE_URL_ENCODED); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andReturn(); - } - - @Test - public void requestWhenCustomConsentPageConfiguredThenRedirect() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomConsentPage.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.add("message.read"); - scopes.add("message.write"); - }).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build(); - this.registeredClientRepository.save(registeredClient); - - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .queryParams(getAuthorizationRequestParameters(registeredClient)) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl).matches("http://localhost/oauth2/consent\\?scope=.+&client_id=.+&state=.+"); - - String locationHeader = URLDecoder.decode(redirectedUrl, StandardCharsets.UTF_8.name()); - UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build(); - MultiValueMap redirectQueryParams = uriComponents.getQueryParams(); - - assertThat(uriComponents.getPath()).isEqualTo(consentPage); - assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.SCOPE)).isEqualTo("message.read message.write"); - assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.CLIENT_ID)) - .isEqualTo(registeredClient.getClientId()); - - String state = extractParameterFromRedirectUri(redirectedUrl, "state"); - OAuth2Authorization authorization = this.authorizationService.findByToken(state, STATE_TOKEN_TYPE); - assertThat(authorization).isNotNull(); - } - - @Test - public void requestWhenCustomConsentCustomizerConfiguredThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomConsentRequest.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .clientSettings(ClientSettings.builder() - .requireAuthorizationConsent(true) - .setting("custom.allowed-authorities", "authority-1 authority-2") - .build()) - .build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = createAuthorization(registeredClient); - OAuth2AuthorizationRequest authorizationRequest = authorization - .getAttribute(OAuth2AuthorizationRequest.class.getName()); - OAuth2AuthorizationRequest updatedAuthorizationRequest = OAuth2AuthorizationRequest.from(authorizationRequest) - .state(STATE_URL_UNENCODED) - .build(); - authorization = OAuth2Authorization.from(authorization) - .attribute(OAuth2AuthorizationRequest.class.getName(), updatedAuthorizationRequest) - .build(); - this.authorizationService.save(authorization); - - MvcResult mvcResult = this.mvc - .perform(post(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .param("authority", "authority-1 authority-2") - .param(OAuth2ParameterNames.STATE, authorization.getAttribute(OAuth2ParameterNames.STATE)) - .with(user("principal"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl) - .matches(authorizationRequest.getRedirectUri() + "\\?code=.{15,}&state=" + STATE_URL_ENCODED); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - mvcResult = this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.access_token").value(new AssertionMatcher() { - @Override - public void assertion(String accessToken) throws AssertionError { - Jwt jwt = OAuth2AuthorizationCodeGrantTests.this.jwtDecoder.decode(accessToken); - assertThat(jwt.getClaimAsStringList(AUTHORITIES_CLAIM)).containsExactlyInAnyOrder("authority-1", - "authority-2"); - } - })) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").doesNotExist()) - .andReturn(); - } - - @Test - public void requestWhenAuthorizationEndpointCustomizedThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomAuthorizationEndpoint.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - TestingAuthenticationToken principal = new TestingAuthenticationToken("principalName", "password"); - OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode("code", Instant.now(), - Instant.now().plus(5, ChronoUnit.MINUTES)); - OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = new OAuth2AuthorizationCodeRequestAuthenticationToken( - "https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal, authorizationCode, - registeredClient.getRedirectUris().iterator().next(), STATE_URL_UNENCODED, - registeredClient.getScopes()); - given(authorizationRequestConverter.convert(any())).willReturn(authorizationCodeRequestAuthenticationResult); - given(authorizationRequestAuthenticationProvider - .supports(eq(OAuth2AuthorizationCodeRequestAuthenticationToken.class))).willReturn(true); - given(authorizationRequestAuthenticationProvider.authenticate(any())) - .willReturn(authorizationCodeRequestAuthenticationResult); - - this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).params(getAuthorizationRequestParameters(registeredClient)) - .with(user("user"))) - .andExpect(status().isOk()); - - verify(authorizationRequestConverter).convert(any()); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authorizationRequestConvertersConsumer).accept(authenticationConvertersCaptor.capture()); - List authenticationConverters = authenticationConvertersCaptor.getValue(); - assertThat(authenticationConverters).allMatch((converter) -> converter == authorizationRequestConverter - || converter instanceof OAuth2AuthorizationCodeRequestAuthenticationConverter - || converter instanceof OAuth2AuthorizationConsentAuthenticationConverter); - - verify(authorizationRequestAuthenticationProvider) - .authenticate(eq(authorizationCodeRequestAuthenticationResult)); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authorizationRequestAuthenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); - List authenticationProviders = authenticationProvidersCaptor.getValue(); - assertThat(authenticationProviders) - .allMatch((provider) -> provider == authorizationRequestAuthenticationProvider - || provider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider - || provider instanceof OAuth2AuthorizationConsentAuthenticationProvider); - - verify(authorizationResponseHandler).onAuthenticationSuccess(any(), any(), - eq(authorizationCodeRequestAuthenticationResult)); - } - - // gh-482 - @Test - public void requestWhenClientObtainsAccessTokenThenClientAuthenticationNotPersisted() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithSecurityContextRepository.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); - this.registeredClientRepository.save(registeredClient); - - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .queryParams(getAuthorizationRequestParameters(registeredClient)) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - - ArgumentCaptor securityContextCaptor = ArgumentCaptor - .forClass(org.springframework.security.core.context.SecurityContext.class); - verify(securityContextRepository, times(1)).saveContext(securityContextCaptor.capture(), any(), any()); - assertThat(securityContextCaptor.getValue().getAuthentication()) - .isInstanceOf(UsernamePasswordAuthenticationToken.class); - reset(securityContextRepository); - - String authorizationCode = extractParameterFromRedirectUri(mvcResult.getResponse().getRedirectedUrl(), "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - mvcResult = this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").doesNotExist()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andReturn(); - - org.springframework.security.core.context.SecurityContext securityContext = securityContextRepository - .loadDeferredContext(mvcResult.getRequest()) - .get(); - assertThat(securityContext.getAuthentication()).isNull(); - } - - @Test - public void requestWhenAuthorizationAndTokenRequestIncludesIssuerPathThenIssuerResolvedWithPath() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient().build(); - this.registeredClientRepository.save(registeredClient); - - String issuer = "https://example.com:8443/issuer1"; - - MvcResult mvcResult = this.mvc - .perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI)) - .queryParams(getAuthorizationRequestParameters(registeredClient)) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - - String authorizationCode = extractParameterFromRedirectUri(mvcResult.getResponse().getRedirectedUrl(), "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - this.mvc - .perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI)) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").doesNotExist()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andReturn(); - - ArgumentCaptor tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class); - verify(this.tokenGenerator).generate(tokenContextCaptor.capture()); - OAuth2TokenContext tokenContext = tokenContextCaptor.getValue(); - assertThat(tokenContext.getAuthorizationServerContext().getIssuer()).isEqualTo(issuer); - } - - @Test - public void requestWhenTokenRequestWithDPoPProofThenReturnDPoPBoundAccessToken() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = createAuthorization(registeredClient); - this.authorizationService.save(authorization); - - String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; - String dPoPProof = generateDPoPProof(tokenEndpointUri); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient)) - .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); - - authorization = this.authorizationService.findById(authorization.getId()); - assertThat(authorization.getAccessToken().getClaims()).containsKey("cnf"); - @SuppressWarnings("unchecked") - Map cnfClaims = (Map) authorization.getAccessToken().getClaims().get("cnf"); - assertThat(cnfClaims).containsKey("jkt"); - String jwkThumbprintClaim = (String) cnfClaims.get("jkt"); - assertThat(jwkThumbprintClaim).isEqualTo(TestJwks.DEFAULT_EC_JWK.toPublicJWK().computeThumbprint().toString()); - } - - @Test - public void requestWhenPushedAuthorizationRequestThenReturnAccessTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequests.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - MvcResult mvcResult = this.mvc - .perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.request_uri").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andReturn(); - - String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri"); - - mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - - String authorizationCode = extractParameterFromRedirectUri(mvcResult.getResponse().getRedirectedUrl(), "code"); - OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andReturn(); - - OAuth2Authorization accessTokenAuthorization = this.authorizationService - .findById(authorizationCodeAuthorization.getId()); - assertThat(accessTokenAuthorization).isNotNull(); - assertThat(accessTokenAuthorization.getAccessToken()).isNotNull(); - - OAuth2Authorization.Token authorizationCodeToken = accessTokenAuthorization - .getToken(OAuth2AuthorizationCode.class); - assertThat(authorizationCodeToken).isNotNull(); - assertThat(authorizationCodeToken.getMetadata().get(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME)) - .isEqualTo(true); - } - - // gh-2182 - @Test - public void requestWhenPushedAuthorizationRequestAndRequiresConsentThenDisplaysConsentPage() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequests.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.add("message.read"); - scopes.add("message.write"); - }).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build(); - this.registeredClientRepository.save(registeredClient); - - MvcResult mvcResult = this.mvc - .perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.request_uri").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andReturn(); - - String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri"); - - String consentPage = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri) - .with(user("user"))) - .andExpect(status().is2xxSuccessful()) - .andReturn() - .getResponse() - .getContentAsString(); - - assertThat(consentPage).contains("Consent required"); - assertThat(consentPage).contains(scopeCheckbox("message.read")); - assertThat(consentPage).contains(scopeCheckbox("message.write")); - } - - // gh-2182 - @Test - public void requestWhenPushedAuthorizationRequestAndCustomConsentPageConfiguredThenRedirect() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestsAndCustomConsentPage.class) - .autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scopes((scopes) -> { - scopes.clear(); - scopes.add("message.read"); - scopes.add("message.write"); - }).clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()).build(); - this.registeredClientRepository.save(registeredClient); - - MvcResult mvcResult = this.mvc - .perform(post("/oauth2/par").params(getAuthorizationRequestParameters(registeredClient)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(registeredClient))) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(status().isCreated()) - .andExpect(jsonPath("$.request_uri").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andReturn(); - - String requestUri = JsonPath.read(mvcResult.getResponse().getContentAsString(), "$.request_uri"); - - mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI) - .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .queryParam(OAuth2ParameterNames.REQUEST_URI, requestUri) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl).matches("http://localhost/oauth2/consent\\?scope=.+&client_id=.+&state=.+"); - - String locationHeader = URLDecoder.decode(redirectedUrl, StandardCharsets.UTF_8.name()); - UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build(); - MultiValueMap redirectQueryParams = uriComponents.getQueryParams(); - - assertThat(uriComponents.getPath()).isEqualTo(consentPage); - assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.SCOPE)).isEqualTo("message.read message.write"); - assertThat(redirectQueryParams.getFirst(OAuth2ParameterNames.CLIENT_ID)) - .isEqualTo(registeredClient.getClientId()); - - String state = extractParameterFromRedirectUri(redirectedUrl, "state"); - OAuth2Authorization authorization = this.authorizationService.findByToken(state, STATE_TOKEN_TYPE); - assertThat(authorization).isNotNull(); - } - - private static OAuth2Authorization createAuthorization(RegisteredClient registeredClient) { - Map additionalParameters = new HashMap<>(); - additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE); - additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); - return TestOAuth2Authorizations.authorization(registeredClient, additionalParameters).build(); - } - - private static String generateDPoPProof(String tokenEndpointUri) { - // @formatter:off - Map publicJwk = TestJwks.DEFAULT_EC_JWK - .toPublicJWK() - .toJSONObject(); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) - .type("dpop+jwt") - .jwk(publicJwk) - .build(); - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuedAt(Instant.now()) - .claim("htm", "POST") - .claim("htu", tokenEndpointUri) - .id(UUID.randomUUID().toString()) - .build(); - // @formatter:on - Jwt jwt = dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - return jwt.getTokenValue(); - } - - private static MultiValueMap getAuthorizationRequestParameters(RegisteredClient registeredClient) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue()); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - parameters.set(OAuth2ParameterNames.STATE, STATE_URL_UNENCODED); - parameters.set(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE); - parameters.set(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); - return parameters; - } - - private static MultiValueMap getTokenRequestParameters(RegisteredClient registeredClient, - OAuth2Authorization authorization) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - parameters.set(OAuth2ParameterNames.CODE, - authorization.getToken(OAuth2AuthorizationCode.class).getToken().getTokenValue()); - parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); - parameters.set(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER); - return parameters; - } - - private static String getAuthorizationHeader(RegisteredClient registeredClient) throws Exception { - String clientId = registeredClient.getClientId(); - String clientSecret = registeredClient.getClientSecret(); - clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8); - clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8); - String credentialsString = clientId + ":" + clientSecret; - byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); - return "Basic " + new String(encodedBytes, StandardCharsets.UTF_8); - } - - private static String scopeCheckbox(String scope) { - return MessageFormat.format( - "", scope); - } - - private String extractParameterFromRedirectUri(String redirectUri, String param) - throws UnsupportedEncodingException { - String locationHeader = URLDecoder.decode(redirectUri, StandardCharsets.UTF_8.name()); - UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build(); - return uriComponents.getQueryParams().getFirst(param); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); - } - - @Bean - OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository); - } - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); - jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); - return jdbcRegisteredClientRepository; - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - JwtDecoder jwtDecoder(JWKSource jwkSource) { - return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); - } - - @Bean - OAuth2TokenCustomizer jwtCustomizer() { - return (context) -> { - if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(context.getAuthorizationGrantType()) - && OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { - Authentication principal = context.getPrincipal(); - Set authorities = new HashSet<>(); - for (GrantedAuthority authority : principal.getAuthorities()) { - authorities.add(authority.getAuthority()); - } - context.getClaims().claim(AUTHORITIES_CLAIM, authorities); - } - }; - } - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithCustomRefreshTokenGenerator - extends AuthorizationServerConfiguration { - - @Bean - JwtEncoder jwtEncoder() { - return jwtEncoder; - } - - @Bean - OAuth2TokenGenerator tokenGenerator() { - JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder()); - jwtGenerator.setJwtCustomizer(jwtCustomizer()); - OAuth2TokenGenerator refreshTokenGenerator = new CustomRefreshTokenGenerator(); - return new DelegatingOAuth2TokenGenerator(jwtGenerator, refreshTokenGenerator); - } - - private static final class CustomRefreshTokenGenerator implements OAuth2TokenGenerator { - - private final StringKeyGenerator refreshTokenGenerator = new Base64StringKeyGenerator( - Base64.getUrlEncoder().withoutPadding(), 96); - - @Nullable - @Override - public OAuth2RefreshToken generate(OAuth2TokenContext context) { - if (!OAuth2TokenType.REFRESH_TOKEN.equals(context.getTokenType())) { - return null; - } - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt - .plus(context.getRegisteredClient().getTokenSettings().getRefreshTokenTimeToLive()); - return new OAuth2RefreshToken(this.refreshTokenGenerator.generateKey(), issuedAt, expiresAt); - } - - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithSecurityContextRepository - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer(Customizer.withDefaults()) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ) - .securityContext((securityContext) -> - securityContext.securityContextRepository(securityContextRepository)); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithTokenGenerator extends AuthorizationServerConfiguration { - - @Bean - JwtEncoder jwtEncoder() { - return jwtEncoder; - } - - @Bean - OAuth2TokenGenerator tokenGenerator() { - JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder()); - jwtGenerator.setJwtCustomizer(jwtCustomizer()); - OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator(); - OAuth2TokenGenerator delegatingTokenGenerator = new DelegatingOAuth2TokenGenerator( - jwtGenerator, refreshTokenGenerator); - return spy(new OAuth2TokenGenerator() { - @Override - public OAuth2Token generate(OAuth2TokenContext context) { - return delegatingTokenGenerator.generate(context); - } - }); - } - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationCustomEndpoints extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return authorizationServerSettings; - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationCustomConsentPage extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .authorizationEndpoint((authorizationEndpoint) -> - authorizationEndpoint.consentPage(consentPage)) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationCustomConsentRequest extends AuthorizationServerConfiguration { - - @Autowired - private OAuth2AuthorizationConsentService authorizationConsentService; - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .authorizationEndpoint((authorizationEndpoint) -> - authorizationEndpoint.authenticationProviders(configureAuthenticationProviders())) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - @Bean - @Override - OAuth2TokenCustomizer jwtCustomizer() { - return (context) -> { - if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(context.getAuthorizationGrantType()) - && OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType())) { - OAuth2AuthorizationConsent authorizationConsent = this.authorizationConsentService - .findById(context.getRegisteredClient().getId(), context.getPrincipal().getName()); - - Set authorities = new HashSet<>(); - for (GrantedAuthority authority : authorizationConsent.getAuthorities()) { - authorities.add(authority.getAuthority()); - } - context.getClaims().claim(AUTHORITIES_CLAIM, authorities); - } - }; - } - - private Consumer> configureAuthenticationProviders() { - return (authenticationProviders) -> authenticationProviders.forEach((authenticationProvider) -> { - if (authenticationProvider instanceof OAuth2AuthorizationConsentAuthenticationProvider) { - ((OAuth2AuthorizationConsentAuthenticationProvider) authenticationProvider) - .setAuthorizationConsentCustomizer(new AuthorizationConsentCustomizer()); - } - }); - } - - static class AuthorizationConsentCustomizer - implements Consumer { - - @Override - public void accept( - OAuth2AuthorizationConsentAuthenticationContext authorizationConsentAuthenticationContext) { - OAuth2AuthorizationConsent.Builder authorizationConsentBuilder = authorizationConsentAuthenticationContext - .getAuthorizationConsent(); - OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication = authorizationConsentAuthenticationContext - .getAuthentication(); - Map additionalParameters = authorizationConsentAuthentication.getAdditionalParameters(); - RegisteredClient registeredClient = authorizationConsentAuthenticationContext.getRegisteredClient(); - ClientSettings clientSettings = registeredClient.getClientSettings(); - - Set requestedAuthorities = authorities((String) additionalParameters.get("authority")); - Set allowedAuthorities = authorities(clientSettings.getSetting("custom.allowed-authorities")); - for (String requestedAuthority : requestedAuthorities) { - if (allowedAuthorities.contains(requestedAuthority)) { - authorizationConsentBuilder.authority(new SimpleGrantedAuthority(requestedAuthority)); - } - } - } - - private static Set authorities(String param) { - Set authorities = new HashSet<>(); - if (param != null) { - List authorityValues = Arrays.asList(param.split(" ")); - authorities.addAll(authorityValues); - } - - return authorities; - } - - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationCustomAuthorizationEndpoint extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .authorizationEndpoint((authorizationEndpoint) -> - authorizationEndpoint - .authorizationRequestConverter(authorizationRequestConverter) - .authorizationRequestConverters(authorizationRequestConvertersConsumer) - .authenticationProvider(authorizationRequestAuthenticationProvider) - .authenticationProviders(authorizationRequestAuthenticationProvidersConsumer) - .authorizationResponseHandler(authorizationResponseHandler) - .errorResponseHandler(authorizationErrorResponseHandler)) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithMultipleIssuersAllowed - extends AuthorizationServerConfigurationWithTokenGenerator { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithPushedAuthorizationRequests - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .pushedAuthorizationRequestEndpoint(Customizer.withDefaults()) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithPushedAuthorizationRequestsAndCustomConsentPage - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .pushedAuthorizationRequestEndpoint(Customizer.withDefaults()) - .authorizationEndpoint((authorizationEndpoint) -> - authorizationEndpoint.consentPage(consentPage)) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerMetadataTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerMetadataTests.java deleted file mode 100644 index 83e4205bef3..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerMetadataTests.java +++ /dev/null @@ -1,337 +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.annotation.web.configurers.oauth2.server.authorization; - -import java.util.function.Consumer; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadata; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimNames; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; - -import static org.hamcrest.CoreMatchers.hasItems; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the OAuth 2.0 Authorization Server Metadata endpoint. - * - * @author Daniel Garnier-Moiroux - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2AuthorizationServerMetadataTests { - - private static final String DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI = "/.well-known/oauth-authorization-server"; - - private static final String ISSUER = "https://example.com"; - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private AuthorizationServerSettings authorizationServerSettings; - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @BeforeAll - public static void setupClass() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenAuthorizationServerMetadataRequestAndIssuerSetThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("issuer").value(ISSUER)) - .andReturn(); - } - - @Test - public void requestWhenAuthorizationServerMetadataRequestIncludesIssuerPathThenMetadataResponseHasIssuerPath() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); - - String host = "https://example.com:8443"; - - String issuerPath = "/issuer1"; - String issuer = host.concat(issuerPath); - this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI).concat(issuerPath))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("issuer").value(issuer)) - .andReturn(); - - issuerPath = "/path1/issuer2"; - issuer = host.concat(issuerPath); - this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI).concat(issuerPath))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("issuer").value(issuer)) - .andReturn(); - - issuerPath = "/path1/path2/issuer3"; - issuer = host.concat(issuerPath); - this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI).concat(issuerPath))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("issuer").value(issuer)) - .andReturn(); - } - - // gh-616 - @Test - public void requestWhenAuthorizationServerMetadataRequestAndMetadataCustomizerSetThenReturnCustomMetadataResponse() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithMetadataCustomizer.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, - hasItems("scope1", "scope2"))); - } - - @Test - public void requestWhenAuthorizationServerMetadataRequestAndClientRegistrationEnabledThenMetadataResponseIncludesRegistrationEndpoint() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithClientRegistrationEnabled.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("$.registration_endpoint") - .value(ISSUER.concat(this.authorizationServerSettings.getClientRegistrationEndpoint()))); - } - - @Test - public void requestWhenAuthorizationServerMetadataRequestAndDeviceCodeGrantEnabledThenMetadataResponseIncludesDeviceAuthorizationEndpoint() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithDeviceCodeGrantEnabled.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("$.device_authorization_endpoint") - .value(ISSUER.concat(this.authorizationServerSettings.getDeviceAuthorizationEndpoint()))) - .andExpect(jsonPath("$.grant_types_supported[4]").value(AuthorizationGrantType.DEVICE_CODE.getValue())); - } - - @Test - public void requestWhenAuthorizationServerMetadataRequestAndPushedAuthorizationRequestEnabledThenMetadataResponseIncludesPushedAuthorizationRequestEndpoint() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath("$.pushed_authorization_request_endpoint") - .value(ISSUER.concat(this.authorizationServerSettings.getPushedAuthorizationRequestEndpoint()))); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - registeredClientRepository.save(registeredClient); - return registeredClientRepository; - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - JwtDecoder jwtDecoder(JWKSource jwkSource) { - return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); - } - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER).build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithMetadataCustomizer extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .authorizationServerMetadataEndpoint((authorizationServerMetadataEndpoint) -> - authorizationServerMetadataEndpoint - .authorizationServerMetadataCustomizer(authorizationServerMetadataCustomizer())) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - private Consumer authorizationServerMetadataCustomizer() { - return (authorizationServerMetadata) -> authorizationServerMetadata.scope("scope1").scope("scope2"); - } - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithClientRegistrationEnabled - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .clientRegistrationEndpoint(Customizer.withDefaults()) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithDeviceCodeGrantEnabled extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .deviceAuthorizationEndpoint(Customizer.withDefaults()) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .pushedAuthorizationRequestEndpoint(Customizer.withDefaults()) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java deleted file mode 100644 index 6e0dc3ae83f..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientCredentialsGrantTests.java +++ /dev/null @@ -1,661 +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.annotation.web.configurers.oauth2.server.authorization; - -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.time.Instant; -import java.util.Base64; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.function.Consumer; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -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.http.HttpHeaders; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.authentication.ClientSecretAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.JwtClientAssertionAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientCredentialsAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2DeviceCodeAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2RefreshTokenAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.PublicClientAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.X509ClientCertificateAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; -import org.springframework.security.oauth2.server.authorization.util.TestX509Certificates; -import org.springframework.security.oauth2.server.authorization.web.authentication.ClientSecretBasicAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.ClientSecretPostAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.JwtClientAssertionAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2DeviceCodeAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenExchangeAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.PublicClientAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.web.authentication.X509ClientCertificateAuthenticationConverter; -import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the OAuth 2.0 Client Credentials Grant. - * - * @author Alexey Nesterov - * @author Joe Grandja - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2ClientCredentialsGrantTests { - - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - private static OAuth2TokenCustomizer jwtCustomizer; - - private static NimbusJwtEncoder dPoPProofJwtEncoder; - - private static AuthenticationConverter authenticationConverter; - - private static Consumer> authenticationConvertersConsumer; - - private static AuthenticationProvider authenticationProvider; - - private static Consumer> authenticationProvidersConsumer; - - private static AuthenticationSuccessHandler authenticationSuccessHandler; - - private static AuthenticationFailureHandler authenticationFailureHandler; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - jwtCustomizer = mock(OAuth2TokenCustomizer.class); - JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); - JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); - dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); - authenticationConverter = mock(AuthenticationConverter.class); - authenticationConvertersConsumer = mock(Consumer.class); - authenticationProvider = mock(AuthenticationProvider.class); - authenticationProvidersConsumer = mock(Consumer.class); - authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - } - - @SuppressWarnings("unchecked") - @BeforeEach - public void setup() { - reset(jwtCustomizer); - reset(authenticationConverter); - reset(authenticationConvertersConsumer); - reset(authenticationProvider); - reset(authenticationProvidersConsumer); - reset(authenticationSuccessHandler); - reset(authenticationFailureHandler); - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenTokenRequestNotAuthenticatedThenUnauthorized() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - this.mvc - .perform(MockMvcRequestBuilders.post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())) - .andExpect(status().isUnauthorized()); - } - - @Test - public void requestWhenTokenRequestValidThenTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(registeredClient); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1 scope2")); - - verify(jwtCustomizer).customize(any()); - } - - @Test - public void requestWhenTokenRequestPostsClientCredentialsThenTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(registeredClient); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .param(OAuth2ParameterNames.CLIENT_SECRET, registeredClient.getClientSecret())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1 scope2")); - - verify(jwtCustomizer).customize(any()); - } - - @Test - public void requestWhenTokenRequestPostsClientCredentialsAndRequiresUpgradingThenClientSecretUpgraded() - throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomPasswordEncoder.class).autowire(); - - String clientSecret = "secret-2"; - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2() - .clientSecret("{noop}" + clientSecret) - .build(); - this.registeredClientRepository.save(registeredClient); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .param(OAuth2ParameterNames.CLIENT_SECRET, clientSecret)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1 scope2")); - - verify(jwtCustomizer).customize(any()); - RegisteredClient updatedRegisteredClient = this.registeredClientRepository - .findByClientId(registeredClient.getClientId()); - assertThat(updatedRegisteredClient.getClientSecret()).startsWith("{bcrypt}"); - } - - @Test - public void requestWhenTokenRequestWithPKIX509ClientCertificateThenTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2() - .clientAuthenticationMethod(ClientAuthenticationMethod.TLS_CLIENT_AUTH) - .clientSettings( - ClientSettings.builder() - .x509CertificateSubjectDN(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE[0].getSubjectX500Principal().getName()) - .build() - ) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .with(SecurityMockMvcRequestPostProcessors.x509(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1 scope2")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1 scope2")); - - verify(jwtCustomizer).customize(any()); - } - - // gh-1635 - @Test - public void requestWhenTokenRequestIncludesBasicClientCredentialsAndX509ClientCertificateThenTokenResponse() - throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(registeredClient); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .with(SecurityMockMvcRequestPostProcessors.x509(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE)) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1 scope2")); - - verify(jwtCustomizer).customize(any()); - } - - @Test - public void requestWhenTokenEndpointCustomizedThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomTokenEndpoint.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = new OAuth2ClientCredentialsAuthenticationToken( - clientPrincipal, null, null); - given(authenticationConverter.convert(any())).willReturn(clientCredentialsAuthentication); - - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token", - Instant.now(), Instant.now().plus(Duration.ofHours(1))); - OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = new OAuth2AccessTokenAuthenticationToken( - registeredClient, clientPrincipal, accessToken); - given(authenticationProvider.supports(eq(OAuth2ClientCredentialsAuthenticationToken.class))).willReturn(true); - given(authenticationProvider.authenticate(any())).willReturn(accessTokenAuthentication); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()); - - verify(authenticationConverter).convert(any()); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); - List authenticationConverters = authenticationConvertersCaptor.getValue(); - assertThat(authenticationConverters).allMatch((converter) -> converter == authenticationConverter - || converter instanceof OAuth2AuthorizationCodeAuthenticationConverter - || converter instanceof OAuth2RefreshTokenAuthenticationConverter - || converter instanceof OAuth2ClientCredentialsAuthenticationConverter - || converter instanceof OAuth2DeviceCodeAuthenticationConverter - || converter instanceof OAuth2TokenExchangeAuthenticationConverter); - - verify(authenticationProvider).authenticate(eq(clientCredentialsAuthentication)); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); - List authenticationProviders = authenticationProvidersCaptor.getValue(); - assertThat(authenticationProviders).allMatch((provider) -> provider == authenticationProvider - || provider instanceof OAuth2AuthorizationCodeAuthenticationProvider - || provider instanceof OAuth2RefreshTokenAuthenticationProvider - || provider instanceof OAuth2ClientCredentialsAuthenticationProvider - || provider instanceof OAuth2DeviceCodeAuthenticationProvider - || provider instanceof OAuth2TokenExchangeAuthenticationProvider); - - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(accessTokenAuthentication)); - } - - @Test - public void requestWhenClientAuthenticationCustomizedThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomClientAuthentication.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - new ClientAuthenticationMethod("custom"), null); - given(authenticationConverter.convert(any())).willReturn(clientPrincipal); - given(authenticationProvider.supports(eq(OAuth2ClientAuthenticationToken.class))).willReturn(true); - given(authenticationProvider.authenticate(any())).willReturn(clientPrincipal); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).param(OAuth2ParameterNames.GRANT_TYPE, - AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())) - .andExpect(status().isOk()); - - verify(authenticationConverter).convert(any()); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); - List authenticationConverters = authenticationConvertersCaptor.getValue(); - assertThat(authenticationConverters).allMatch((converter) -> converter == authenticationConverter - || converter instanceof JwtClientAssertionAuthenticationConverter - || converter instanceof ClientSecretBasicAuthenticationConverter - || converter instanceof ClientSecretPostAuthenticationConverter - || converter instanceof PublicClientAuthenticationConverter - || converter instanceof X509ClientCertificateAuthenticationConverter); - - verify(authenticationProvider).authenticate(eq(clientPrincipal)); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); - List authenticationProviders = authenticationProvidersCaptor.getValue(); - assertThat(authenticationProviders).allMatch((provider) -> provider == authenticationProvider - || provider instanceof JwtClientAssertionAuthenticationProvider - || provider instanceof X509ClientCertificateAuthenticationProvider - || provider instanceof ClientSecretAuthenticationProvider - || provider instanceof PublicClientAuthenticationProvider); - - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(clientPrincipal)); - } - - @Test - public void requestWhenTokenRequestIncludesIssuerPathThenIssuerResolvedWithPath() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(registeredClient); - - String issuer = "https://example.com:8443/issuer1"; - - this.mvc - .perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI)) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1 scope2")); - - ArgumentCaptor jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class); - verify(jwtCustomizer).customize(jwtEncodingContextCaptor.capture()); - JwtEncodingContext jwtEncodingContext = jwtEncodingContextCaptor.getValue(); - assertThat(jwtEncodingContext.getAuthorizationServerContext().getIssuer()).isEqualTo(issuer); - } - - @Test - public void requestWhenTokenRequestWithDPoPProofThenReturnDPoPBoundAccessToken() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(registeredClient); - - String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; - String dPoPProof = generateDPoPProof(tokenEndpointUri); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1 scope2") - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret())) - .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); - } - - private static String generateDPoPProof(String tokenEndpointUri) { - // @formatter:off - Map publicJwk = TestJwks.DEFAULT_EC_JWK - .toPublicJWK() - .toJSONObject(); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) - .type("dpop+jwt") - .jwk(publicJwk) - .build(); - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuedAt(Instant.now()) - .claim("htm", "POST") - .claim("htu", tokenEndpointUri) - .id(UUID.randomUUID().toString()) - .build(); - // @formatter:on - Jwt jwt = dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - return jwt.getTokenValue(); - } - - private static String encodeBasicAuth(String clientId, String secret) throws Exception { - clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); - secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name()); - String credentialsString = clientId + ":" + secret; - byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); - return new String(encodedBytes, StandardCharsets.UTF_8); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); - } - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); - jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); - return jdbcRegisteredClientRepository; - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - OAuth2TokenCustomizer jwtCustomizer() { - return jwtCustomizer; - } - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationCustomTokenEndpoint extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .tokenEndpoint((tokenEndpoint) -> - tokenEndpoint - .accessTokenRequestConverter(authenticationConverter) - .accessTokenRequestConverters(authenticationConvertersConsumer) - .authenticationProvider(authenticationProvider) - .authenticationProviders(authenticationProvidersConsumer) - .accessTokenResponseHandler(authenticationSuccessHandler) - .errorResponseHandler(authenticationFailureHandler)) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationCustomPasswordEncoder extends AuthorizationServerConfiguration { - - @Override - PasswordEncoder passwordEncoder() { - return PasswordEncoderFactories.createDelegatingPasswordEncoder(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationCustomClientAuthentication extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - authenticationSuccessHandler = spy(authenticationSuccessHandler()); - - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .clientAuthentication((clientAuthentication) -> - clientAuthentication - .authenticationConverter(authenticationConverter) - .authenticationConverters(authenticationConvertersConsumer) - .authenticationProvider(authenticationProvider) - .authenticationProviders(authenticationProvidersConsumer) - .authenticationSuccessHandler(authenticationSuccessHandler) - .errorResponseHandler(authenticationFailureHandler)) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - private AuthenticationSuccessHandler authenticationSuccessHandler() { - return new AuthenticationSuccessHandler() { - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException, ServletException { - org.springframework.security.core.context.SecurityContext securityContext = SecurityContextHolder - .createEmptyContext(); - securityContext.setAuthentication(authentication); - SecurityContextHolder.setContext(securityContext); - } - }; - } - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientRegistrationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientRegistrationTests.java deleted file mode 100644 index a0512af77a8..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2ClientRegistrationTests.java +++ /dev/null @@ -1,776 +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.annotation.web.configurers.oauth2.server.authorization; - -import java.time.Duration; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import jakarta.servlet.http.HttpServletResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.assertj.core.data.TemporalUnitWithinOffset; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.mock.http.MockHttpOutputMessage; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2ClientRegistration; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientRegistrationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.converter.OAuth2ClientRegistrationRegisteredClientConverter; -import org.springframework.security.oauth2.server.authorization.converter.RegisteredClientOAuth2ClientRegistrationConverter; -import org.springframework.security.oauth2.server.authorization.http.converter.OAuth2ClientRegistrationHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientRegistrationAuthenticationConverter; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.CollectionUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.containsString; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for OAuth 2.0 Dynamic Client Registration. - * - * @author Joe Grandja - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2ClientRegistrationTests { - - private static final String ISSUER = "https://example.com:8443/issuer1"; - - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - private static final String DEFAULT_OAUTH2_CLIENT_REGISTRATION_ENDPOINT_URI = "/oauth2/register"; - - private static final HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - private static final HttpMessageConverter clientRegistrationHttpMessageConverter = new OAuth2ClientRegistrationHttpMessageConverter(); - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - private static AuthenticationConverter authenticationConverter; - - private static Consumer> authenticationConvertersConsumer; - - private static AuthenticationProvider authenticationProvider; - - private static Consumer> authenticationProvidersConsumer; - - private static AuthenticationSuccessHandler authenticationSuccessHandler; - - private static AuthenticationFailureHandler authenticationFailureHandler; - - private MockWebServer server; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - authenticationConverter = mock(AuthenticationConverter.class); - authenticationConvertersConsumer = mock(Consumer.class); - authenticationProvider = mock(AuthenticationProvider.class); - authenticationProvidersConsumer = mock(Consumer.class); - authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - } - - @BeforeEach - public void setup() throws Exception { - this.server = new MockWebServer(); - this.server.start(); - given(authenticationProvider.supports(OAuth2ClientRegistrationAuthenticationToken.class)).willReturn(true); - } - - @AfterEach - public void tearDown() throws Exception { - this.server.shutdown(); - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - reset(authenticationConverter); - reset(authenticationConvertersConsumer); - reset(authenticationProvider); - reset(authenticationProvidersConsumer); - reset(authenticationSuccessHandler); - reset(authenticationFailureHandler); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenClientRegistrationRequestAuthorizedThenClientRegistrationResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - OAuth2ClientRegistration clientRegistration = OAuth2ClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - OAuth2ClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - - assertClientRegistrationResponse(clientRegistration, clientRegistrationResponse); - } - - @Test - public void requestWhenOpenClientRegistrationRequestThenClientRegistrationResponse() throws Exception { - this.spring.register(OpenClientRegistrationConfiguration.class).autowire(); - - // @formatter:off - OAuth2ClientRegistration clientRegistration = OAuth2ClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - MvcResult mvcResult = this.mvc - .perform(post(ISSUER.concat(DEFAULT_OAUTH2_CLIENT_REGISTRATION_ENDPOINT_URI)) - .contentType(MediaType.APPLICATION_JSON) - .content(getClientRegistrationRequestContent(clientRegistration))) - .andExpect(status().isCreated()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andReturn(); - - OAuth2ClientRegistration clientRegistrationResponse = readClientRegistrationResponse(mvcResult.getResponse()); - - assertClientRegistrationResponse(clientRegistration, clientRegistrationResponse); - } - - @Test - public void requestWhenClientRegistrationEndpointCustomizedThenUsed() throws Exception { - this.spring.register(CustomClientRegistrationConfiguration.class).autowire(); - - // @formatter:off - OAuth2ClientRegistration clientRegistration = OAuth2ClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - willAnswer((invocation) -> { - HttpServletResponse response = invocation.getArgument(1, HttpServletResponse.class); - ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - httpResponse.setStatusCode(HttpStatus.CREATED); - new OAuth2ClientRegistrationHttpMessageConverter().write(clientRegistration, null, httpResponse); - return null; - }).given(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); - - registerClient(clientRegistration); - - verify(authenticationConverter).convert(any()); - ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); - List authenticationConverters = authenticationConvertersCaptor.getValue(); - assertThat(authenticationConverters).hasSize(2) - .allMatch((converter) -> converter == authenticationConverter - || converter instanceof OAuth2ClientRegistrationAuthenticationConverter); - - verify(authenticationProvider).authenticate(any()); - ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); - List authenticationProviders = authenticationProvidersCaptor.getValue(); - assertThat(authenticationProviders).hasSize(2) - .allMatch((provider) -> provider == authenticationProvider - || provider instanceof OAuth2ClientRegistrationAuthenticationProvider); - - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); - verifyNoInteractions(authenticationFailureHandler); - } - - @Test - public void requestWhenClientRegistrationEndpointCustomizedWithAuthenticationFailureHandlerThenUsed() - throws Exception { - this.spring.register(CustomClientRegistrationConfiguration.class).autowire(); - - given(authenticationProvider.authenticate(any())).willThrow(new OAuth2AuthenticationException("error")); - - this.mvc.perform(post(ISSUER.concat(DEFAULT_OAUTH2_CLIENT_REGISTRATION_ENDPOINT_URI)).with(jwt())); - - verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any()); - verifyNoInteractions(authenticationSuccessHandler); - } - - @Test - public void requestWhenClientRegistersWithSecretThenClientAuthenticationSuccess() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - OAuth2ClientRegistration clientRegistration = OAuth2ClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - OAuth2ClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - - this.mvc - .perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI)) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1") - .with(httpBasic(clientRegistrationResponse.getClientId(), - clientRegistrationResponse.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1")) - .andReturn(); - } - - @Test - public void requestWhenClientRegistersWithCustomMetadataThenSavedToRegisteredClient() throws Exception { - this.spring.register(CustomClientMetadataConfiguration.class).autowire(); - - // @formatter:off - OAuth2ClientRegistration clientRegistration = OAuth2ClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .claim("custom-metadata-name-1", "value-1") - .claim("custom-metadata-name-2", "value-2") - .claim("non-registered-custom-metadata", "value-3") - .build(); - // @formatter:on - - OAuth2ClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - - RegisteredClient registeredClient = this.registeredClientRepository - .findByClientId(clientRegistrationResponse.getClientId()); - - assertClientRegistrationResponse(clientRegistration, clientRegistrationResponse); - assertThat(clientRegistrationResponse.getClaim("custom-metadata-name-1")).isEqualTo("value-1"); - assertThat(clientRegistrationResponse.getClaim("custom-metadata-name-2")).isEqualTo("value-2"); - assertThat(clientRegistrationResponse.getClaim("non-registered-custom-metadata")).isNull(); - - assertThat(registeredClient.getClientSettings().getSetting("custom-metadata-name-1")) - .isEqualTo("value-1"); - assertThat(registeredClient.getClientSettings().getSetting("custom-metadata-name-2")) - .isEqualTo("value-2"); - assertThat(registeredClient.getClientSettings().getSetting("non-registered-custom-metadata")).isNull(); - } - - @Test - public void requestWhenClientRegistersWithSecretExpirationThenClientRegistrationResponse() throws Exception { - this.spring.register(ClientSecretExpirationConfiguration.class).autowire(); - - // @formatter:off - OAuth2ClientRegistration clientRegistration = OAuth2ClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - OAuth2ClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - - Instant expectedSecretExpiryDate = Instant.now().plus(Duration.ofHours(24)); - TemporalUnitWithinOffset allowedDelta = new TemporalUnitWithinOffset(1, ChronoUnit.MINUTES); - - // Returned response contains expiration date - assertThat(clientRegistrationResponse.getClientSecretExpiresAt()).isNotNull() - .isCloseTo(expectedSecretExpiryDate, allowedDelta); - - RegisteredClient registeredClient = this.registeredClientRepository - .findByClientId(clientRegistrationResponse.getClientId()); - - // Persisted RegisteredClient contains expiration date - assertThat(registeredClient).isNotNull(); - assertThat(registeredClient.getClientSecretExpiresAt()).isNotNull() - .isCloseTo(expectedSecretExpiryDate, allowedDelta); - } - - private OAuth2ClientRegistration registerClient(OAuth2ClientRegistration clientRegistration) throws Exception { - // ***** (1) Obtain the "initial" access token used for registering the client - - String clientRegistrationScope = "client.create"; - // @formatter:off - RegisteredClient clientRegistrar = RegisteredClient.withId("client-registrar-1") - .clientId("client-registrar-1") - .clientSecret("{noop}secret") - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .scope(clientRegistrationScope) - .build(); - // @formatter:on - this.registeredClientRepository.save(clientRegistrar); - - MvcResult mvcResult = this.mvc - .perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI)) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, clientRegistrationScope) - .with(httpBasic("client-registrar-1", "secret"))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value(clientRegistrationScope)) - .andReturn(); - - OAuth2AccessToken accessToken = readAccessTokenResponse(mvcResult.getResponse()).getAccessToken(); - - // ***** (2) Register the client - - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setBearerAuth(accessToken.getTokenValue()); - - // Register the client - mvcResult = this.mvc - .perform(post(ISSUER.concat(DEFAULT_OAUTH2_CLIENT_REGISTRATION_ENDPOINT_URI)).headers(httpHeaders) - .contentType(MediaType.APPLICATION_JSON) - .content(getClientRegistrationRequestContent(clientRegistration))) - .andExpect(status().isCreated()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andReturn(); - - return readClientRegistrationResponse(mvcResult.getResponse()); - } - - private static void assertClientRegistrationResponse(OAuth2ClientRegistration clientRegistrationRequest, - OAuth2ClientRegistration clientRegistrationResponse) { - assertThat(clientRegistrationResponse.getClientId()).isNotNull(); - assertThat(clientRegistrationResponse.getClientIdIssuedAt()).isNotNull(); - assertThat(clientRegistrationResponse.getClientSecret()).isNotNull(); - assertThat(clientRegistrationResponse.getClientSecretExpiresAt()).isNull(); - assertThat(clientRegistrationResponse.getClientName()).isEqualTo(clientRegistrationRequest.getClientName()); - assertThat(clientRegistrationResponse.getRedirectUris()) - .containsExactlyInAnyOrderElementsOf(clientRegistrationRequest.getRedirectUris()); - assertThat(clientRegistrationResponse.getGrantTypes()) - .containsExactlyInAnyOrderElementsOf(clientRegistrationRequest.getGrantTypes()); - assertThat(clientRegistrationResponse.getResponseTypes()) - .containsExactly(OAuth2AuthorizationResponseType.CODE.getValue()); - assertThat(clientRegistrationResponse.getScopes()) - .containsExactlyInAnyOrderElementsOf(clientRegistrationRequest.getScopes()); - assertThat(clientRegistrationResponse.getTokenEndpointAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); - } - - private static OAuth2AccessTokenResponse readAccessTokenResponse(MockHttpServletResponse response) - throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); - } - - private static byte[] getClientRegistrationRequestContent(OAuth2ClientRegistration clientRegistration) - throws Exception { - MockHttpOutputMessage httpRequest = new MockHttpOutputMessage(); - clientRegistrationHttpMessageConverter.write(clientRegistration, null, httpRequest); - return httpRequest.getBodyAsBytes(); - } - - private static OAuth2ClientRegistration readClientRegistrationResponse(MockHttpServletResponse response) - throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return clientRegistrationHttpMessageConverter.read(OAuth2ClientRegistration.class, httpResponse); - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class CustomClientRegistrationConfiguration extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - @Override - public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .clientRegistrationEndpoint((clientRegistration) -> - clientRegistration - .clientRegistrationRequestConverter(authenticationConverter) - .clientRegistrationRequestConverters(authenticationConvertersConsumer) - .authenticationProvider(authenticationProvider) - .authenticationProviders(authenticationProvidersConsumer) - .clientRegistrationResponseHandler(authenticationSuccessHandler) - .errorResponseHandler(authenticationFailureHandler) - ) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class CustomClientMetadataConfiguration extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - @Override - public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .clientRegistrationEndpoint((clientRegistration) -> - clientRegistration - .authenticationProviders(configureClientRegistrationConverters()) - ) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - private Consumer> configureClientRegistrationConverters() { - // @formatter:off - return (authenticationProviders) -> - authenticationProviders.forEach((authenticationProvider) -> { - List supportedCustomClientMetadata = List.of("custom-metadata-name-1", "custom-metadata-name-2"); - if (authenticationProvider instanceof OAuth2ClientRegistrationAuthenticationProvider provider) { - provider.setRegisteredClientConverter(new CustomRegisteredClientConverter(supportedCustomClientMetadata)); - provider.setClientRegistrationConverter(new CustomClientRegistrationConverter(supportedCustomClientMetadata)); - } - }); - // @formatter:on - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class ClientSecretExpirationConfiguration extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - @Override - public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .clientRegistrationEndpoint((clientRegistration) -> - clientRegistration - .authenticationProviders(configureClientRegistrationConverters()) - ) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - private Consumer> configureClientRegistrationConverters() { - // @formatter:off - return (authenticationProviders) -> - authenticationProviders.forEach((authenticationProvider) -> { - if (authenticationProvider instanceof OAuth2ClientRegistrationAuthenticationProvider provider) { - provider.setRegisteredClientConverter(new ClientSecretExpirationRegisteredClientConverter()); - } - }); - // @formatter:on - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class OpenClientRegistrationConfiguration extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - @Override - public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .clientRegistrationEndpoint((clientRegistration) -> - clientRegistration - .openRegistrationAllowed(true) - ) - ) - .authorizeHttpRequests((authorize) -> - authorize - .requestMatchers("/**/oauth2/register").permitAll() - .anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .clientRegistrationEndpoint(Customizer.withDefaults()) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); - JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - registeredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); - registeredClientRepository.save(registeredClient); - return registeredClientRepository; - } - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - JwtDecoder jwtDecoder(JWKSource jwkSource) { - return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); - } - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - @Bean - PasswordEncoder passwordEncoder() { - return PasswordEncoderFactories.createDelegatingPasswordEncoder(); - } - - } - - private static final class CustomRegisteredClientConverter - implements Converter { - - private final OAuth2ClientRegistrationRegisteredClientConverter delegate = new OAuth2ClientRegistrationRegisteredClientConverter(); - - private final List supportedCustomClientMetadata; - - private CustomRegisteredClientConverter(List supportedCustomClientMetadata) { - this.supportedCustomClientMetadata = supportedCustomClientMetadata; - } - - @Override - public RegisteredClient convert(OAuth2ClientRegistration clientRegistration) { - RegisteredClient registeredClient = this.delegate.convert(clientRegistration); - - ClientSettings.Builder clientSettingsBuilder = ClientSettings - .withSettings(registeredClient.getClientSettings().getSettings()); - if (!CollectionUtils.isEmpty(this.supportedCustomClientMetadata)) { - clientRegistration.getClaims().forEach((claim, value) -> { - if (this.supportedCustomClientMetadata.contains(claim)) { - clientSettingsBuilder.setting(claim, value); - } - }); - } - - return RegisteredClient.from(registeredClient).clientSettings(clientSettingsBuilder.build()).build(); - } - - } - - private static final class CustomClientRegistrationConverter - implements Converter { - - private final RegisteredClientOAuth2ClientRegistrationConverter delegate = new RegisteredClientOAuth2ClientRegistrationConverter(); - - private final List supportedCustomClientMetadata; - - private CustomClientRegistrationConverter(List supportedCustomClientMetadata) { - this.supportedCustomClientMetadata = supportedCustomClientMetadata; - } - - @Override - public OAuth2ClientRegistration convert(RegisteredClient registeredClient) { - OAuth2ClientRegistration clientRegistration = this.delegate.convert(registeredClient); - - Map clientMetadata = new HashMap<>(clientRegistration.getClaims()); - if (!CollectionUtils.isEmpty(this.supportedCustomClientMetadata)) { - Map clientSettings = registeredClient.getClientSettings().getSettings(); - this.supportedCustomClientMetadata.forEach((customClaim) -> { - if (clientSettings.containsKey(customClaim)) { - clientMetadata.put(customClaim, clientSettings.get(customClaim)); - } - }); - } - - return OAuth2ClientRegistration.withClaims(clientMetadata).build(); - } - - } - - /** - * This customization adds client secret expiration time by setting - * {@code RegisteredClient.clientSecretExpiresAt} during - * {@code OAuth2ClientRegistration} -> {@code RegisteredClient} conversion - */ - private static final class ClientSecretExpirationRegisteredClientConverter - implements Converter { - - private static final OAuth2ClientRegistrationRegisteredClientConverter delegate = new OAuth2ClientRegistrationRegisteredClientConverter(); - - @Override - public RegisteredClient convert(OAuth2ClientRegistration clientRegistration) { - RegisteredClient registeredClient = delegate.convert(clientRegistration); - RegisteredClient.Builder registeredClientBuilder = RegisteredClient.from(registeredClient); - - Instant clientSecretExpiresAt = Instant.now().plus(Duration.ofHours(24)); - registeredClientBuilder.clientSecretExpiresAt(clientSecretExpiresAt); - - return registeredClientBuilder.build(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2DeviceCodeGrantTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2DeviceCodeGrantTests.java deleted file mode 100644 index a2938fddc83..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2DeviceCodeGrantTests.java +++ /dev/null @@ -1,734 +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.annotation.web.configurers.oauth2.server.authorization; - -import java.security.Principal; -import java.time.Instant; -import java.util.Map; -import java.util.UUID; -import java.util.function.Consumer; -import java.util.function.Function; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2DeviceCode; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.OAuth2UserCode; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2DeviceAuthorizationResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.core.http.converter.OAuth2DeviceAuthorizationResponseHttpMessageConverter; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for OAuth 2.0 Device Grant. - * - * @author Steve Riesenberg - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2DeviceCodeGrantTests { - - private static final String DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI = "/oauth2/device_authorization"; - - private static final String DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI = "/oauth2/device_verification"; - - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - private static final OAuth2TokenType DEVICE_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.DEVICE_CODE); - - private static final String USER_CODE = "ABCD-EFGH"; - - private static final String STATE = "123"; - - private static final String DEVICE_CODE = "abc-XYZ"; - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - private static NimbusJwtEncoder dPoPProofJwtEncoder; - - private static final HttpMessageConverter deviceAuthorizationResponseHttpMessageConverter = new OAuth2DeviceAuthorizationResponseHttpMessageConverter(); - - private static final HttpMessageConverter accessTokenResponseHttpMessageConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private OAuth2AuthorizationService authorizationService; - - @Autowired - private OAuth2AuthorizationConsentService authorizationConsentService; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); - JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); - dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); - // @formatter:off - db = new EmbeddedDatabaseBuilder() - .generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql") - .addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - // @formatter:on - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_authorization_consent"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenDeviceAuthorizationRequestNotAuthenticatedThenUnauthorized() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - - // @formatter:off - this.mvc.perform(post(DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI) - .params(parameters)) - .andExpect(status().isUnauthorized()); - // @formatter:on - } - - @Test - public void requestWhenRegisteredClientMissingThenUnauthorized() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - - // @formatter:off - this.mvc.perform(post(DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI) - .params(parameters) - .headers(withClientAuth(registeredClient))) - .andExpect(status().isUnauthorized()); - // @formatter:on - } - - @Test - public void requestWhenDeviceAuthorizationRequestValidThenReturnDeviceAuthorizationResponse() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - - String issuer = "https://example.com:8443/issuer1"; - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(issuer.concat(DEFAULT_DEVICE_AUTHORIZATION_ENDPOINT_URI)) - .params(parameters) - .headers(withClientAuth(registeredClient))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.device_code").isNotEmpty()) - .andExpect(jsonPath("$.user_code").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNumber()) - .andExpect(jsonPath("$.verification_uri").isNotEmpty()) - .andExpect(jsonPath("$.verification_uri_complete").isNotEmpty()) - .andReturn(); - // @formatter:on - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.OK); - OAuth2DeviceAuthorizationResponse deviceAuthorizationResponse = deviceAuthorizationResponseHttpMessageConverter - .read(OAuth2DeviceAuthorizationResponse.class, httpResponse); - String userCode = deviceAuthorizationResponse.getUserCode().getTokenValue(); - assertThat(userCode).matches("[A-Z]{4}-[A-Z]{4}"); - assertThat(deviceAuthorizationResponse.getVerificationUri()) - .isEqualTo("https://example.com:8443/oauth2/device_verification"); - assertThat(deviceAuthorizationResponse.getVerificationUriComplete()) - .isEqualTo("https://example.com:8443/oauth2/device_verification?user_code=" + userCode); - - String deviceCode = deviceAuthorizationResponse.getDeviceCode().getTokenValue(); - OAuth2Authorization authorization = this.authorizationService.findByToken(deviceCode, DEVICE_CODE_TOKEN_TYPE); - assertThat(authorization.getToken(OAuth2DeviceCode.class)).isNotNull(); - assertThat(authorization.getToken(OAuth2UserCode.class)).isNotNull(); - } - - @Test - public void requestWhenDeviceVerificationRequestUnauthenticatedThenUnauthorized() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(300); - // @formatter:off - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName(registeredClient.getClientId()) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) - .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt)) - .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE); - - // @formatter:off - this.mvc.perform(get(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI) - .queryParams(parameters)) - .andExpect(status().isUnauthorized()); - // @formatter:on - } - - @Test - public void requestWhenDeviceVerificationRequestValidThenDisplaysConsentPage() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(300); - // @formatter:off - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName(registeredClient.getClientId()) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) - .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt)) - .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE); - - String issuer = "https://example.com:8443/issuer1"; - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get(issuer.concat(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI)) - .queryParams(parameters) - .with(user("user"))) - .andExpect(status().isOk()) - .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML)) - .andReturn(); - // @formatter:on - - String responseHtml = mvcResult.getResponse().getContentAsString(); - assertThat(responseHtml).contains("Consent required"); - - OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); - assertThat(updatedAuthorization.getPrincipalName()).isEqualTo("user"); - assertThat(updatedAuthorization).isNotNull(); - // @formatter:off - assertThat(updatedAuthorization.getToken(OAuth2UserCode.class)) - .extracting(isInvalidated()) - .isEqualTo(false); - // @formatter:on - } - - @Test - public void requestWhenDeviceAuthorizationConsentRequestUnauthenticatedThenBadRequest() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(300); - // @formatter:off - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName("user") - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) - .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt)) - .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) - .attribute(OAuth2ParameterNames.STATE, STATE) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.SCOPE, registeredClient.getScopes().iterator().next()); - parameters.set(OAuth2ParameterNames.STATE, STATE); - - // @formatter:off - this.mvc.perform(post(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI) - .params(parameters)) - .andExpect(status().isBadRequest()); - // @formatter:on - } - - @Test - public void requestWhenDeviceAuthorizationConsentRequestValidThenRedirectsToSuccessPage() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(300); - // @formatter:off - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName("user") - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) - .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt)) - .attribute(OAuth2ParameterNames.SCOPE, registeredClient.getScopes()) - .attribute(OAuth2ParameterNames.STATE, STATE) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.USER_CODE, USER_CODE); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.SCOPE, registeredClient.getScopes().iterator().next()); - parameters.set(OAuth2ParameterNames.STATE, STATE); - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(DEFAULT_DEVICE_VERIFICATION_ENDPOINT_URI) - .params(parameters) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - // @formatter:on - - assertThat(mvcResult.getResponse().getHeader(HttpHeaders.LOCATION)).isEqualTo("/?success"); - - OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); - assertThat(updatedAuthorization).isNotNull(); - // @formatter:off - assertThat(updatedAuthorization.getToken(OAuth2UserCode.class)) - .extracting(isInvalidated()) - .isEqualTo(true); - // @formatter:on - } - - @Test - public void requestWhenAccessTokenRequestUnauthenticatedThenUnauthorized() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(300); - // @formatter:off - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName(registeredClient.getClientId()) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) - .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt), withInvalidated()) - .authorizedScopes(registeredClient.getScopes()) - .attribute(Principal.class.getName(), new UsernamePasswordAuthenticationToken("user", null)) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.DEVICE_CODE.getValue()); - parameters.set(OAuth2ParameterNames.DEVICE_CODE, DEVICE_CODE); - - // @formatter:off - this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(parameters)) - .andExpect(status().isUnauthorized()); - // @formatter:on - } - - @Test - public void requestWhenAccessTokenRequestValidThenReturnAccessTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(300); - // @formatter:off - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName(registeredClient.getClientId()) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) - .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt), withInvalidated()) - .authorizedScopes(registeredClient.getScopes()) - .attribute(Principal.class.getName(), new UsernamePasswordAuthenticationToken("user", null)) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - // @formatter:off - OAuth2AuthorizationConsent authorizationConsent = - OAuth2AuthorizationConsent.withId(registeredClient.getClientId(), "user") - .scope(registeredClient.getScopes().iterator().next()) - .build(); - // @formatter:on - this.authorizationConsentService.save(authorizationConsent); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.DEVICE_CODE.getValue()); - parameters.set(OAuth2ParameterNames.DEVICE_CODE, DEVICE_CODE); - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(parameters) - .headers(withClientAuth(registeredClient))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNumber()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andReturn(); - // @formatter:on - - OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); - assertThat(updatedAuthorization).isNotNull(); - assertThat(updatedAuthorization.getAccessToken()).isNotNull(); - assertThat(updatedAuthorization.getRefreshToken()).isNotNull(); - // @formatter:off - assertThat(updatedAuthorization.getToken(OAuth2DeviceCode.class)) - .extracting(isInvalidated()) - .isEqualTo(true); - // @formatter:on - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.OK); - OAuth2AccessTokenResponse accessTokenResponse = accessTokenResponseHttpMessageConverter - .read(OAuth2AccessTokenResponse.class, httpResponse); - - String accessToken = accessTokenResponse.getAccessToken().getTokenValue(); - OAuth2Authorization accessTokenAuthorization = this.authorizationService.findByToken(accessToken, - OAuth2TokenType.ACCESS_TOKEN); - assertThat(accessTokenAuthorization).isEqualTo(updatedAuthorization); - } - - @Test - public void requestWhenAccessTokenRequestWithDPoPProofThenReturnDPoPBoundAccessToken() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .build(); - // @formatter:on - this.registeredClientRepository.save(registeredClient); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(300); - // @formatter:off - OAuth2Authorization authorization = OAuth2Authorization.withRegisteredClient(registeredClient) - .principalName(registeredClient.getClientId()) - .authorizationGrantType(AuthorizationGrantType.DEVICE_CODE) - .token(new OAuth2DeviceCode(DEVICE_CODE, issuedAt, expiresAt)) - .token(new OAuth2UserCode(USER_CODE, issuedAt, expiresAt), withInvalidated()) - .authorizedScopes(registeredClient.getScopes()) - .attribute(Principal.class.getName(), new UsernamePasswordAuthenticationToken("user", null)) - .build(); - // @formatter:on - this.authorizationService.save(authorization); - - // @formatter:off - OAuth2AuthorizationConsent authorizationConsent = - OAuth2AuthorizationConsent.withId(registeredClient.getClientId(), "user") - .scope(registeredClient.getScopes().iterator().next()) - .build(); - // @formatter:on - this.authorizationConsentService.save(authorizationConsent); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.DEVICE_CODE.getValue()); - parameters.set(OAuth2ParameterNames.DEVICE_CODE, DEVICE_CODE); - - String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; - String dPoPProof = generateDPoPProof(tokenEndpointUri); - - // @formatter:off - this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(parameters) - .headers(withClientAuth(registeredClient)) - .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); - // @formatter:on - - authorization = this.authorizationService.findById(authorization.getId()); - assertThat(authorization.getAccessToken().getClaims()).containsKey("cnf"); - @SuppressWarnings("unchecked") - Map cnfClaims = (Map) authorization.getAccessToken().getClaims().get("cnf"); - assertThat(cnfClaims).containsKey("jkt"); - String jwkThumbprintClaim = (String) cnfClaims.get("jkt"); - assertThat(jwkThumbprintClaim).isEqualTo(TestJwks.DEFAULT_EC_JWK.toPublicJWK().computeThumbprint().toString()); - } - - private static String generateDPoPProof(String tokenEndpointUri) { - // @formatter:off - Map publicJwk = TestJwks.DEFAULT_EC_JWK - .toPublicJWK() - .toJSONObject(); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) - .type("dpop+jwt") - .jwk(publicJwk) - .build(); - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuedAt(Instant.now()) - .claim("htm", "POST") - .claim("htu", tokenEndpointUri) - .id(UUID.randomUUID().toString()) - .build(); - // @formatter:on - Jwt jwt = dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - return jwt.getTokenValue(); - } - - private static HttpHeaders withClientAuth(RegisteredClient registeredClient) { - HttpHeaders headers = new HttpHeaders(); - headers.setBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()); - return headers; - } - - private static Consumer> withInvalidated() { - return (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true); - } - - private static Function, Boolean> isInvalidated() { - return (token) -> token.getMetadata(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME); - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .deviceAuthorizationEndpoint(Customizer.withDefaults()) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - return new JdbcRegisteredClientRepository(jdbcOperations); - } - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); - } - - @Bean - OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository); - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().build(); - } - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .deviceAuthorizationEndpoint(Customizer.withDefaults()) - .deviceVerificationEndpoint(Customizer.withDefaults()) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2RefreshTokenGrantTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2RefreshTokenGrantTests.java deleted file mode 100644 index 16df4207b9c..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2RefreshTokenGrantTests.java +++ /dev/null @@ -1,619 +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.annotation.web.configurers.oauth2.server.authorization; - -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.Principal; -import java.security.PublicKey; -import java.time.Instant; -import java.util.Base64; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.lang.Nullable; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.Transient; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.TestKeys; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the OAuth 2.0 Refresh Token Grant. - * - * @author Alexey Nesterov - * @since 7.0 - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2RefreshTokenGrantTests { - - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - private static final String DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI = "/oauth2/revoke"; - - private static final String AUTHORITIES_CLAIM = "authorities"; - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - private static NimbusJwtDecoder jwtDecoder; - - private static NimbusJwtEncoder dPoPProofJwtEncoder; - - private static HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private OAuth2AuthorizationService authorizationService; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - jwtDecoder = NimbusJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY).build(); - JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); - JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); - dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenRefreshTokenRequestValidThenReturnAccessTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - this.authorizationService.save(authorization); - - MvcResult mvcResult = this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andReturn(); - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter - .read(OAuth2AccessTokenResponse.class, httpResponse); - - // Assert user authorities was propagated as claim in JWT - Jwt jwt = jwtDecoder.decode(accessTokenResponse.getAccessToken().getTokenValue()); - List authoritiesClaim = jwt.getClaim(AUTHORITIES_CLAIM); - Authentication principal = authorization.getAttribute(Principal.class.getName()); - Set userAuthorities = new HashSet<>(); - for (GrantedAuthority authority : principal.getAuthorities()) { - userAuthorities.add(authority.getAuthority()); - } - assertThat(authoritiesClaim).containsExactlyInAnyOrderElementsOf(userAuthorities); - } - - // gh-432 - @Test - public void requestWhenRevokeAndRefreshThenAccessTokenActive() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - this.authorizationService.save(authorization); - - OAuth2AccessToken token = authorization.getAccessToken().getToken(); - OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN; - - this.mvc - .perform(post(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI) - .params(getTokenRevocationRequestParameters(token, tokenType)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()); - - OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); - OAuth2Authorization.Token accessToken = updatedAuthorization.getAccessToken(); - assertThat(accessToken.isActive()).isTrue(); - } - - // gh-1430 - @Test - public void requestWhenRefreshTokenRequestWithPublicClientThenReturnAccessTokenResponse() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithPublicClientAuthentication.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient() - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - this.authorizationService.save(authorization); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").isNotEmpty()); - } - - @Test - public void requestWhenRefreshTokenRequestWithPublicClientAndDPoPProofThenReturnDPoPBoundAccessToken() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithPublicClientAuthentication.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient() - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.DPOP, - "dpop-bound-access-token", Instant.now(), Instant.now().plusSeconds(300)); - Map accessTokenClaims = new HashMap<>(); - Map cnfClaim = new HashMap<>(); - cnfClaim.put("jkt", TestJwks.DEFAULT_EC_JWK.toPublicJWK().computeThumbprint().toString()); - accessTokenClaims.put("cnf", cnfClaim); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, accessToken, accessTokenClaims) - .build(); - this.authorizationService.save(authorization); - - String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; - String dPoPProof = generateDPoPProof(tokenEndpointUri); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); - - authorization = this.authorizationService.findById(authorization.getId()); - assertThat(authorization.getAccessToken().getClaims()).containsKey("cnf"); - @SuppressWarnings("unchecked") - Map cnfClaims = (Map) authorization.getAccessToken().getClaims().get("cnf"); - assertThat(cnfClaims).containsKey("jkt"); - String jwkThumbprintClaim = (String) cnfClaims.get("jkt"); - assertThat(jwkThumbprintClaim).isEqualTo(TestJwks.DEFAULT_EC_JWK.toPublicJWK().computeThumbprint().toString()); - } - - @Test - public void requestWhenRefreshTokenRequestWithPublicClientAndDPoPProofAndAccessTokenNotBoundThenBadRequest() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithPublicClientAuthentication.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient() - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - this.authorizationService.save(authorization); - - String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; - String dPoPProof = generateDPoPProof(tokenEndpointUri); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.error").value(OAuth2ErrorCodes.INVALID_DPOP_PROOF)) - .andExpect(jsonPath("$.error_description").value("jkt claim is missing.")); - } - - @Test - public void requestWhenRefreshTokenRequestWithPublicClientAndDPoPProofAndDifferentPublicKeyThenBadRequest() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithPublicClientAuthentication.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredPublicClient() - .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) - .build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.DPOP, - "dpop-bound-access-token", Instant.now(), Instant.now().plusSeconds(300)); - Map accessTokenClaims = new HashMap<>(); - // Bind access token to different public key - PublicKey publicKey = TestJwks.DEFAULT_RSA_JWK.toPublicKey(); - Map cnfClaim = new HashMap<>(); - cnfClaim.put("jkt", computeSHA256(publicKey)); - accessTokenClaims.put("cnf", cnfClaim); - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(registeredClient, accessToken, accessTokenClaims) - .build(); - this.authorizationService.save(authorization); - - String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; - String dPoPProof = generateDPoPProof(tokenEndpointUri); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) - .param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) - .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.error").value(OAuth2ErrorCodes.INVALID_DPOP_PROOF)) - .andExpect(jsonPath("$.error_description").value("jwk header is invalid.")); - } - - @Test - public void requestWhenRefreshTokenRequestWithDPoPProofThenReturnDPoPBoundAccessToken() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - this.authorizationService.save(authorization); - - String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; - String dPoPProof = generateDPoPProof(tokenEndpointUri); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getRefreshTokenRequestParameters(authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret())) - .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); - - authorization = this.authorizationService.findById(authorization.getId()); - assertThat(authorization.getAccessToken().getClaims()).containsKey("cnf"); - @SuppressWarnings("unchecked") - Map cnfClaims = (Map) authorization.getAccessToken().getClaims().get("cnf"); - assertThat(cnfClaims).containsKey("jkt"); - } - - private static String generateDPoPProof(String tokenEndpointUri) { - // @formatter:off - Map publicJwk = TestJwks.DEFAULT_EC_JWK - .toPublicJWK() - .toJSONObject(); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) - .type("dpop+jwt") - .jwk(publicJwk) - .build(); - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuedAt(Instant.now()) - .claim("htm", "POST") - .claim("htu", tokenEndpointUri) - .id(UUID.randomUUID().toString()) - .build(); - // @formatter:on - Jwt jwt = dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - return jwt.getTokenValue(); - } - - private static String computeSHA256(PublicKey publicKey) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] digest = md.digest(publicKey.getEncoded()); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } - - private static MultiValueMap getRefreshTokenRequestParameters(OAuth2Authorization authorization) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.REFRESH_TOKEN.getValue()); - parameters.set(OAuth2ParameterNames.REFRESH_TOKEN, authorization.getRefreshToken().getToken().getTokenValue()); - return parameters; - } - - private static MultiValueMap getTokenRevocationRequestParameters(OAuth2Token token, - OAuth2TokenType tokenType) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.TOKEN, token.getTokenValue()); - parameters.set(OAuth2ParameterNames.TOKEN_TYPE_HINT, tokenType.getValue()); - return parameters; - } - - private static String encodeBasicAuth(String clientId, String secret) throws Exception { - clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); - secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name()); - String credentialsString = clientId + ":" + secret; - byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); - return new String(encodedBytes, StandardCharsets.UTF_8); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); - } - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); - jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); - return jdbcRegisteredClientRepository; - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - OAuth2TokenCustomizer jwtCustomizer() { - return (context) -> { - if (AuthorizationGrantType.REFRESH_TOKEN.equals(context.getAuthorizationGrantType())) { - Authentication principal = context.getPrincipal(); - Set authorities = new HashSet<>(); - for (GrantedAuthority authority : principal.getAuthorities()) { - authorities.add(authority.getAuthority()); - } - context.getClaims().claim(AUTHORITIES_CLAIM, authorities); - } - }; - } - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithPublicClientAuthentication - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain( - HttpSecurity http, RegisteredClientRepository registeredClientRepository) throws Exception { - - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .clientAuthentication((clientAuthentication) -> - clientAuthentication - .authenticationConverter( - new PublicClientRefreshTokenAuthenticationConverter()) - .authenticationProvider( - new PublicClientRefreshTokenAuthenticationProvider(registeredClientRepository))) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @Transient - private static final class PublicClientRefreshTokenAuthenticationToken extends OAuth2ClientAuthenticationToken { - - private PublicClientRefreshTokenAuthenticationToken(String clientId) { - super(clientId, ClientAuthenticationMethod.NONE, null, null); - } - - private PublicClientRefreshTokenAuthenticationToken(RegisteredClient registeredClient) { - super(registeredClient, ClientAuthenticationMethod.NONE, null); - } - - } - - private static final class PublicClientRefreshTokenAuthenticationConverter implements AuthenticationConverter { - - @Nullable - @Override - public Authentication convert(HttpServletRequest request) { - // grant_type (REQUIRED) - String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE); - if (!AuthorizationGrantType.REFRESH_TOKEN.getValue().equals(grantType)) { - return null; - } - - // client_id (REQUIRED) - String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID); - if (!StringUtils.hasText(clientId)) { - return null; - } - - return new PublicClientRefreshTokenAuthenticationToken(clientId); - } - - } - - private static final class PublicClientRefreshTokenAuthenticationProvider implements AuthenticationProvider { - - private final RegisteredClientRepository registeredClientRepository; - - private PublicClientRefreshTokenAuthenticationProvider(RegisteredClientRepository registeredClientRepository) { - Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); - this.registeredClientRepository = registeredClientRepository; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - PublicClientRefreshTokenAuthenticationToken publicClientAuthentication = (PublicClientRefreshTokenAuthenticationToken) authentication; - - if (!ClientAuthenticationMethod.NONE.equals(publicClientAuthentication.getClientAuthenticationMethod())) { - return null; - } - - String clientId = publicClientAuthentication.getPrincipal().toString(); - RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(clientId); - if (registeredClient == null) { - throwInvalidClient(OAuth2ParameterNames.CLIENT_ID); - } - - if (!registeredClient.getClientAuthenticationMethods() - .contains(publicClientAuthentication.getClientAuthenticationMethod())) { - throwInvalidClient("authentication_method"); - } - - return new PublicClientRefreshTokenAuthenticationToken(registeredClient); - } - - @Override - public boolean supports(Class authentication) { - return PublicClientRefreshTokenAuthenticationToken.class.isAssignableFrom(authentication); - } - - private static void throwInvalidClient(String parameterName) { - OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT, - "Public client authentication failed: " + parameterName, null); - throw new OAuth2AuthenticationException(error); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenExchangeGrantTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenExchangeGrantTests.java deleted file mode 100644 index e2a9fd1b304..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenExchangeGrantTests.java +++ /dev/null @@ -1,472 +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.annotation.web.configurers.oauth2.server.authorization; - -import java.security.Principal; -import java.time.Instant; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import java.util.function.Consumer; -import java.util.function.Function; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimNames; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for OAuth 2.0 Token Exchange Grant. - * - * @author Steve Riesenberg - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2TokenExchangeGrantTests { - - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - private static final String RESOURCE = "https://mydomain.com/resource"; - - private static final String AUDIENCE = "audience"; - - private static final String SUBJECT_TOKEN = "EfYu_0jEL"; - - private static final String ACTOR_TOKEN = "JlNE_xR1f"; - - private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token"; - - private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt"; - - private static NimbusJwtEncoder dPoPProofJwtEncoder; - - public final SpringTestContext spring = new SpringTestContext(this); - - private final HttpMessageConverter accessTokenResponseHttpMessageConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private OAuth2AuthorizationService authorizationService; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - AuthorizationServerConfiguration.JWK_SOURCE = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - JWKSet clientJwkSet = new JWKSet(TestJwks.DEFAULT_EC_JWK); - JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet); - dPoPProofJwtEncoder = new NimbusJwtEncoder(clientJwkSource); - // @formatter:off - AuthorizationServerConfiguration.DB = new EmbeddedDatabaseBuilder() - .generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-consent-schema.sql") - .addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - // @formatter:on - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_authorization_consent"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - AuthorizationServerConfiguration.DB.shutdown(); - } - - @Test - public void requestWhenAccessTokenRequestNotAuthenticatedThenUnauthorized() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - - // @formatter:off - this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(parameters)) - .andExpect(status().isUnauthorized()); - // @formatter:on - } - - @Test - public void requestWhenAccessTokenRequestValidAndNoActorTokenThenReturnAccessTokenResponseForImpersonation() - throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - this.registeredClientRepository.save(registeredClient); - - UsernamePasswordAuthenticationToken userPrincipal = createUserPrincipal("user"); - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient) - .attribute(Principal.class.getName(), userPrincipal) - .build(); - this.authorizationService.save(subjectAuthorization); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN, - subjectAuthorization.getAccessToken().getToken().getTokenValue()); - parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - parameters.set(OAuth2ParameterNames.RESOURCE, RESOURCE); - parameters.set(OAuth2ParameterNames.AUDIENCE, AUDIENCE); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(parameters) - .headers(withClientAuth(registeredClient))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").doesNotExist()) - .andExpect(jsonPath("$.expires_in").isNumber()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.issued_token_type").isNotEmpty()) - .andReturn(); - // @formatter:on - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.OK); - OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseHttpMessageConverter - .read(OAuth2AccessTokenResponse.class, httpResponse); - - String accessToken = accessTokenResponse.getAccessToken().getTokenValue(); - OAuth2Authorization authorization = this.authorizationService.findByToken(accessToken, - OAuth2TokenType.ACCESS_TOKEN); - assertThat(authorization).isNotNull(); - assertThat(authorization.getAccessToken()).isNotNull(); - assertThat(authorization.getAccessToken().getClaims()).isNotNull(); - // We do not populate claims (e.g. `aud`) based on the resource or audience - // parameters - assertThat(authorization.getAccessToken().getClaims().get(OAuth2TokenClaimNames.AUD)) - .isEqualTo(List.of(registeredClient.getClientId())); - assertThat(authorization.getRefreshToken()).isNull(); - assertThat(authorization.getAttribute(Principal.class.getName())).isEqualTo(userPrincipal); - } - - @Test - public void requestWhenAccessTokenRequestValidAndActorTokenThenReturnAccessTokenResponseForDelegation() - throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - this.registeredClientRepository.save(registeredClient); - - UsernamePasswordAuthenticationToken userPrincipal = createUserPrincipal("user"); - UsernamePasswordAuthenticationToken adminPrincipal = createUserPrincipal("admin"); - Map actorTokenClaims = new HashMap<>(); - actorTokenClaims.put(OAuth2TokenClaimNames.ISS, "issuer2"); - actorTokenClaims.put(OAuth2TokenClaimNames.SUB, "admin"); - Map subjectTokenClaims = new HashMap<>(); - subjectTokenClaims.put(OAuth2TokenClaimNames.ISS, "issuer1"); - subjectTokenClaims.put(OAuth2TokenClaimNames.SUB, "user"); - subjectTokenClaims.put("may_act", actorTokenClaims); - OAuth2AccessToken subjectToken = createAccessToken(SUBJECT_TOKEN); - OAuth2AccessToken actorToken = createAccessToken(ACTOR_TOKEN); - // @formatter:off - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient, subjectToken, subjectTokenClaims) - .id(UUID.randomUUID().toString()) - .attribute(Principal.class.getName(), userPrincipal) - .build(); - OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient, actorToken, actorTokenClaims) - .id(UUID.randomUUID().toString()) - .attribute(Principal.class.getName(), adminPrincipal) - .build(); - // @formatter:on - this.authorizationService.save(subjectAuthorization); - this.authorizationService.save(actorAuthorization); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); - parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - parameters.set(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); - parameters.set(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(parameters) - .headers(withClientAuth(registeredClient))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").doesNotExist()) - .andExpect(jsonPath("$.expires_in").isNumber()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.issued_token_type").isNotEmpty()) - .andReturn(); - // @formatter:on - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.OK); - OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseHttpMessageConverter - .read(OAuth2AccessTokenResponse.class, httpResponse); - - String accessToken = accessTokenResponse.getAccessToken().getTokenValue(); - OAuth2Authorization authorization = this.authorizationService.findByToken(accessToken, - OAuth2TokenType.ACCESS_TOKEN); - assertThat(authorization).isNotNull(); - assertThat(authorization.getAccessToken()).isNotNull(); - assertThat(authorization.getAccessToken().getClaims()).isNotNull(); - assertThat(authorization.getAccessToken().getClaims().get("act")).isNotNull(); - assertThat(authorization.getRefreshToken()).isNull(); - assertThat(authorization.getAttribute(Principal.class.getName())) - .isInstanceOf(OAuth2TokenExchangeCompositeAuthenticationToken.class); - } - - @Test - public void requestWhenAccessTokenRequestWithDPoPProofThenReturnDPoPBoundAccessToken() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .build(); - this.registeredClientRepository.save(registeredClient); - - UsernamePasswordAuthenticationToken userPrincipal = createUserPrincipal("user"); - UsernamePasswordAuthenticationToken adminPrincipal = createUserPrincipal("admin"); - Map actorTokenClaims = new HashMap<>(); - actorTokenClaims.put(OAuth2TokenClaimNames.ISS, "issuer2"); - actorTokenClaims.put(OAuth2TokenClaimNames.SUB, "admin"); - Map subjectTokenClaims = new HashMap<>(); - subjectTokenClaims.put(OAuth2TokenClaimNames.ISS, "issuer1"); - subjectTokenClaims.put(OAuth2TokenClaimNames.SUB, "user"); - subjectTokenClaims.put("may_act", actorTokenClaims); - OAuth2AccessToken subjectToken = createAccessToken(SUBJECT_TOKEN); - OAuth2AccessToken actorToken = createAccessToken(ACTOR_TOKEN); - // @formatter:off - OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient, subjectToken, subjectTokenClaims) - .id(UUID.randomUUID().toString()) - .attribute(Principal.class.getName(), userPrincipal) - .build(); - OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient, actorToken, actorTokenClaims) - .id(UUID.randomUUID().toString()) - .attribute(Principal.class.getName(), adminPrincipal) - .build(); - // @formatter:on - this.authorizationService.save(subjectAuthorization); - this.authorizationService.save(actorAuthorization); - - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue()); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN, SUBJECT_TOKEN); - parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE); - parameters.set(OAuth2ParameterNames.ACTOR_TOKEN, ACTOR_TOKEN); - parameters.set(OAuth2ParameterNames.ACTOR_TOKEN_TYPE, ACCESS_TOKEN_TYPE_VALUE); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - - String tokenEndpointUri = "http://localhost" + DEFAULT_TOKEN_ENDPOINT_URI; - String dPoPProof = generateDPoPProof(tokenEndpointUri); - - // @formatter:off - this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(parameters) - .headers(withClientAuth(registeredClient)) - .header(OAuth2AccessToken.TokenType.DPOP.getValue(), dPoPProof)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.token_type").value(OAuth2AccessToken.TokenType.DPOP.getValue())); - // @formatter:on - } - - private static OAuth2AccessToken createAccessToken(String tokenValue) { - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plusSeconds(300); - return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, tokenValue, issuedAt, expiresAt); - } - - private static UsernamePasswordAuthenticationToken createUserPrincipal(String username) { - User user = new User(username, "", AuthorityUtils.createAuthorityList("ROLE_USER")); - return UsernamePasswordAuthenticationToken.authenticated(user, null, user.getAuthorities()); - } - - private static HttpHeaders withClientAuth(RegisteredClient registeredClient) { - HttpHeaders headers = new HttpHeaders(); - headers.setBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()); - return headers; - } - - private static Consumer> withInvalidated() { - return (metadata) -> metadata.put(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME, true); - } - - private static Function, Boolean> isInvalidated() { - return (token) -> token.getMetadata(OAuth2Authorization.Token.INVALIDATED_METADATA_NAME); - } - - private static String generateDPoPProof(String tokenEndpointUri) { - // @formatter:off - Map publicJwk = TestJwks.DEFAULT_EC_JWK - .toPublicJWK() - .toJSONObject(); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) - .type("dpop+jwt") - .jwk(publicJwk) - .build(); - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuedAt(Instant.now()) - .claim("htm", "POST") - .claim("htu", tokenEndpointUri) - .id(UUID.randomUUID().toString()) - .build(); - // @formatter:on - Jwt jwt = dPoPProofJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - return jwt.getTokenValue(); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - static JWKSource JWK_SOURCE; - - static EmbeddedDatabase DB; - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - return new JdbcRegisteredClientRepository(jdbcOperations); - } - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); - } - - @Bean - OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository); - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(DB); - } - - @Bean - JWKSource jwkSource() { - return JWK_SOURCE; - } - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenIntrospectionTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenIntrospectionTests.java deleted file mode 100644 index e4bd35d062f..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenIntrospectionTests.java +++ /dev/null @@ -1,585 +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.annotation.web.configurers.oauth2.server.authorization; - -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.function.Consumer; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -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.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.OAuth2TokenIntrospectionClaimNames; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenIntrospection; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenIntrospectionAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.http.converter.OAuth2TokenIntrospectionHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat; -import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenIntrospectionAuthenticationConverter; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the OAuth 2.0 Token Introspection endpoint. - * - * @author Gerardo Roza - * @author Joe Grandja - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2TokenIntrospectionTests { - - private static EmbeddedDatabase db; - - private static OAuth2TokenCustomizer accessTokenCustomizer; - - private static AuthenticationConverter authenticationConverter; - - private static Consumer> authenticationConvertersConsumer; - - private static AuthenticationProvider authenticationProvider; - - private static Consumer> authenticationProvidersConsumer; - - private static AuthenticationSuccessHandler authenticationSuccessHandler; - - private static AuthenticationFailureHandler authenticationFailureHandler; - - private static final HttpMessageConverter tokenIntrospectionHttpResponseConverter = new OAuth2TokenIntrospectionHttpMessageConverter(); - - private static final HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private OAuth2AuthorizationService authorizationService; - - @Autowired - private AuthorizationServerSettings authorizationServerSettings; - - @BeforeAll - public static void init() { - authenticationConverter = mock(AuthenticationConverter.class); - authenticationConvertersConsumer = mock(Consumer.class); - authenticationProvider = mock(AuthenticationProvider.class); - authenticationProvidersConsumer = mock(Consumer.class); - authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - accessTokenCustomizer = mock(OAuth2TokenCustomizer.class); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - } - - @SuppressWarnings("unchecked") - @BeforeEach - public void setup() { - reset(authenticationConverter); - reset(authenticationConvertersConsumer); - reset(authenticationProvider); - reset(authenticationProvidersConsumer); - reset(authenticationSuccessHandler); - reset(authenticationFailureHandler); - reset(accessTokenCustomizer); - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenIntrospectValidAccessTokenThenActive() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2() - .clientSecret("secret-2") - .build(); - this.registeredClientRepository.save(introspectRegisteredClient); - - RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build(); - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(Duration.ofHours(1)); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", - issuedAt, expiresAt, new HashSet<>(Arrays.asList("scope1", "scope2"))); - // @formatter:off - OAuth2TokenClaimsSet accessTokenClaims = OAuth2TokenClaimsSet.builder() - .issuer("https://provider.com") - .subject("subject") - .audience(Collections.singletonList(authorizedRegisteredClient.getClientId())) - .issuedAt(issuedAt) - .notBefore(issuedAt) - .expiresAt(expiresAt) - .claim(OAuth2TokenIntrospectionClaimNames.SCOPE, accessToken.getScopes()) - .id("id") - .build(); - // @formatter:on - OAuth2Authorization authorization = TestOAuth2Authorizations - .authorization(authorizedRegisteredClient, accessToken, accessTokenClaims.getClaims()) - .build(); - this.registeredClientRepository.save(authorizedRegisteredClient); - this.authorizationService.save(authorization); - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(this.authorizationServerSettings.getTokenIntrospectionEndpoint()) - .params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) - .andExpect(status().isOk()) - .andReturn(); - // @formatter:on - - OAuth2TokenIntrospection tokenIntrospectionResponse = readTokenIntrospectionResponse(mvcResult); - assertThat(tokenIntrospectionResponse.isActive()).isTrue(); - assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo(authorizedRegisteredClient.getClientId()); - assertThat(tokenIntrospectionResponse.getUsername()).isNull(); - assertThat(tokenIntrospectionResponse.getIssuedAt()).isBetween(accessTokenClaims.getIssuedAt().minusSeconds(1), - accessTokenClaims.getIssuedAt().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getExpiresAt()).isBetween( - accessTokenClaims.getExpiresAt().minusSeconds(1), accessTokenClaims.getExpiresAt().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getScopes()).containsExactlyInAnyOrderElementsOf(accessToken.getScopes()); - assertThat(tokenIntrospectionResponse.getTokenType()).isEqualTo(accessToken.getTokenType().getValue()); - assertThat(tokenIntrospectionResponse.getNotBefore()).isBetween( - accessTokenClaims.getNotBefore().minusSeconds(1), accessTokenClaims.getNotBefore().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getSubject()).isEqualTo(accessTokenClaims.getSubject()); - assertThat(tokenIntrospectionResponse.getAudience()) - .containsExactlyInAnyOrderElementsOf(accessTokenClaims.getAudience()); - assertThat(tokenIntrospectionResponse.getIssuer()).isEqualTo(accessTokenClaims.getIssuer()); - assertThat(tokenIntrospectionResponse.getId()).isEqualTo(accessTokenClaims.getId()); - } - - @Test - public void requestWhenIntrospectValidRefreshTokenThenActive() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2() - .clientSecret("secret-2") - .build(); - this.registeredClientRepository.save(introspectRegisteredClient); - - RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build(); - OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken(); - this.registeredClientRepository.save(authorizedRegisteredClient); - this.authorizationService.save(authorization); - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(this.authorizationServerSettings.getTokenIntrospectionEndpoint()) - .params(getTokenIntrospectionRequestParameters(refreshToken, OAuth2TokenType.REFRESH_TOKEN)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) - .andExpect(status().isOk()) - .andReturn(); - // @formatter:on - - OAuth2TokenIntrospection tokenIntrospectionResponse = readTokenIntrospectionResponse(mvcResult); - assertThat(tokenIntrospectionResponse.isActive()).isTrue(); - assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo(authorizedRegisteredClient.getClientId()); - assertThat(tokenIntrospectionResponse.getUsername()).isNull(); - assertThat(tokenIntrospectionResponse.getIssuedAt()).isBetween(refreshToken.getIssuedAt().minusSeconds(1), - refreshToken.getIssuedAt().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getExpiresAt()).isBetween(refreshToken.getExpiresAt().minusSeconds(1), - refreshToken.getExpiresAt().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getScopes()).isNull(); - assertThat(tokenIntrospectionResponse.getTokenType()).isNull(); - assertThat(tokenIntrospectionResponse.getNotBefore()).isNull(); - assertThat(tokenIntrospectionResponse.getSubject()).isNull(); - assertThat(tokenIntrospectionResponse.getAudience()).isNull(); - assertThat(tokenIntrospectionResponse.getIssuer()).isNull(); - assertThat(tokenIntrospectionResponse.getId()).isNull(); - } - - @Test - public void requestWhenObtainReferenceAccessTokenAndIntrospectThenActive() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - TokenSettings tokenSettings = TokenSettings.builder() - .accessTokenFormat(OAuth2TokenFormat.REFERENCE) - .build(); - RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient() - .tokenSettings(tokenSettings) - .clientSettings(ClientSettings.builder().requireProofKey(false).build()) - .build(); - // @formatter:on - this.registeredClientRepository.save(authorizedRegisteredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build(); - this.authorizationService.save(authorization); - - // @formatter:off - MvcResult mvcResult = this.mvc.perform(post(this.authorizationServerSettings.getTokenEndpoint()) - .params(getAuthorizationCodeTokenRequestParameters(authorizedRegisteredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(authorizedRegisteredClient))) - .andExpect(status().isOk()) - .andReturn(); - // @formatter:on - - OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(mvcResult); - OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken(); - - RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(introspectRegisteredClient); - - // @formatter:off - mvcResult = this.mvc.perform(post(this.authorizationServerSettings.getTokenIntrospectionEndpoint()) - .params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) - .andExpect(status().isOk()) - .andReturn(); - // @formatter:on - - OAuth2TokenIntrospection tokenIntrospectionResponse = readTokenIntrospectionResponse(mvcResult); - - ArgumentCaptor accessTokenClaimsContextCaptor = ArgumentCaptor - .forClass(OAuth2TokenClaimsContext.class); - verify(accessTokenCustomizer).customize(accessTokenClaimsContextCaptor.capture()); - - OAuth2TokenClaimsContext accessTokenClaimsContext = accessTokenClaimsContextCaptor.getValue(); - OAuth2TokenClaimsSet accessTokenClaims = accessTokenClaimsContext.getClaims().build(); - - assertThat(tokenIntrospectionResponse.isActive()).isTrue(); - assertThat(tokenIntrospectionResponse.getClientId()).isEqualTo(authorizedRegisteredClient.getClientId()); - assertThat(tokenIntrospectionResponse.getUsername()).isNull(); - assertThat(tokenIntrospectionResponse.getIssuedAt()).isBetween(accessTokenClaims.getIssuedAt().minusSeconds(1), - accessTokenClaims.getIssuedAt().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getExpiresAt()).isBetween( - accessTokenClaims.getExpiresAt().minusSeconds(1), accessTokenClaims.getExpiresAt().plusSeconds(1)); - List scopes = new ArrayList<>(accessTokenClaims.getClaim(OAuth2ParameterNames.SCOPE)); - assertThat(tokenIntrospectionResponse.getScopes()).containsExactlyInAnyOrderElementsOf(scopes); - assertThat(tokenIntrospectionResponse.getTokenType()).isEqualTo(accessToken.getTokenType().getValue()); - assertThat(tokenIntrospectionResponse.getNotBefore()).isBetween( - accessTokenClaims.getNotBefore().minusSeconds(1), accessTokenClaims.getNotBefore().plusSeconds(1)); - assertThat(tokenIntrospectionResponse.getSubject()).isEqualTo(accessTokenClaims.getSubject()); - assertThat(tokenIntrospectionResponse.getAudience()) - .containsExactlyInAnyOrderElementsOf(accessTokenClaims.getAudience()); - assertThat(tokenIntrospectionResponse.getIssuer()).isEqualTo(accessTokenClaims.getIssuer()); - assertThat(tokenIntrospectionResponse.getId()).isEqualTo(accessTokenClaims.getId()); - } - - @Test - public void requestWhenTokenIntrospectionEndpointCustomizedThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomTokenIntrospectionEndpoint.class).autowire(); - - RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(introspectRegisteredClient); - - RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(authorizedRegisteredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build(); - this.authorizationService.save(authorization); - - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(introspectRegisteredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, introspectRegisteredClient.getClientSecret()); - OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication = new OAuth2TokenIntrospectionAuthenticationToken( - accessToken.getTokenValue(), clientPrincipal, null, null); - - given(authenticationConverter.convert(any())).willReturn(tokenIntrospectionAuthentication); - given(authenticationProvider.supports(eq(OAuth2TokenIntrospectionAuthenticationToken.class))).willReturn(true); - given(authenticationProvider.authenticate(any())).willReturn(tokenIntrospectionAuthentication); - - // @formatter:off - this.mvc.perform(post(this.authorizationServerSettings.getTokenIntrospectionEndpoint()) - .params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) - .andExpect(status().isOk()); - // @formatter:on - - verify(authenticationConverter).convert(any()); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); - List authenticationConverters = authenticationConvertersCaptor.getValue(); - assertThat(authenticationConverters).allMatch((converter) -> converter == authenticationConverter - || converter instanceof OAuth2TokenIntrospectionAuthenticationConverter); - - verify(authenticationProvider).authenticate(eq(tokenIntrospectionAuthentication)); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); - List authenticationProviders = authenticationProvidersCaptor.getValue(); - assertThat(authenticationProviders).allMatch((provider) -> provider == authenticationProvider - || provider instanceof OAuth2TokenIntrospectionAuthenticationProvider); - - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), - eq(tokenIntrospectionAuthentication)); - } - - @Test - public void requestWhenIntrospectionRequestIncludesIssuerPathThenActive() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomTokenIntrospectionEndpoint.class).autowire(); - - RegisteredClient introspectRegisteredClient = TestRegisteredClients.registeredClient2().build(); - this.registeredClientRepository.save(introspectRegisteredClient); - - RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(authorizedRegisteredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(authorizedRegisteredClient).build(); - this.authorizationService.save(authorization); - - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(introspectRegisteredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, introspectRegisteredClient.getClientSecret()); - OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication = new OAuth2TokenIntrospectionAuthenticationToken( - accessToken.getTokenValue(), clientPrincipal, null, null); - - given(authenticationConverter.convert(any())).willReturn(tokenIntrospectionAuthentication); - given(authenticationProvider.supports(eq(OAuth2TokenIntrospectionAuthenticationToken.class))).willReturn(true); - given(authenticationProvider.authenticate(any())).willReturn(tokenIntrospectionAuthentication); - - String issuer = "https://example.com:8443/issuer1"; - - // @formatter:off - this.mvc.perform(post(issuer.concat(this.authorizationServerSettings.getTokenIntrospectionEndpoint())) - .params(getTokenIntrospectionRequestParameters(accessToken, OAuth2TokenType.ACCESS_TOKEN)) - .header(HttpHeaders.AUTHORIZATION, getAuthorizationHeader(introspectRegisteredClient))) - .andExpect(status().isOk()); - // @formatter:on - } - - private static MultiValueMap getTokenIntrospectionRequestParameters(OAuth2Token token, - OAuth2TokenType tokenType) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.TOKEN, token.getTokenValue()); - parameters.set(OAuth2ParameterNames.TOKEN_TYPE_HINT, tokenType.getValue()); - return parameters; - } - - private static OAuth2TokenIntrospection readTokenIntrospectionResponse(MvcResult mvcResult) throws Exception { - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - return tokenIntrospectionHttpResponseConverter.read(OAuth2TokenIntrospection.class, httpResponse); - } - - private static MultiValueMap getAuthorizationCodeTokenRequestParameters( - RegisteredClient registeredClient, OAuth2Authorization authorization) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - parameters.set(OAuth2ParameterNames.CODE, - authorization.getToken(OAuth2AuthorizationCode.class).getToken().getTokenValue()); - parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); - return parameters; - } - - private static OAuth2AccessTokenResponse readAccessTokenResponse(MvcResult mvcResult) throws Exception { - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - return accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); - } - - private static String getAuthorizationHeader(RegisteredClient registeredClient) throws Exception { - String clientId = registeredClient.getClientId(); - String clientSecret = registeredClient.getClientSecret(); - clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); - clientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8.name()); - String credentialsString = clientId + ":" + clientSecret; - byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); - return "Basic " + new String(encodedBytes, StandardCharsets.UTF_8); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); - } - - @Bean - OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository); - } - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); - jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); - return jdbcRegisteredClientRepository; - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().tokenIntrospectionEndpoint("/test/introspect").build(); - } - - @Bean - OAuth2TokenCustomizer accessTokenCustomizer() { - return accessTokenCustomizer; - } - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationCustomTokenIntrospectionEndpoint - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .tokenIntrospectionEndpoint((tokenIntrospectionEndpoint) -> - tokenIntrospectionEndpoint - .introspectionRequestConverter(authenticationConverter) - .introspectionRequestConverters(authenticationConvertersConsumer) - .authenticationProvider(authenticationProvider) - .authenticationProviders(authenticationProvidersConsumer) - .introspectionResponseHandler(authenticationSuccessHandler) - .errorResponseHandler(authenticationFailureHandler)) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - @Override - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder() - .multipleIssuersAllowed(true) - .tokenIntrospectionEndpoint("/test/introspect") - .build(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenRevocationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenRevocationTests.java deleted file mode 100644 index 8ff0e389f18..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2TokenRevocationTests.java +++ /dev/null @@ -1,386 +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.annotation.web.configurers.oauth2.server.authorization; - -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.List; -import java.util.function.Consumer; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -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.http.HttpHeaders; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenRevocationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2TokenRevocationAuthenticationConverter; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the OAuth 2.0 Token Revocation endpoint. - * - * @author Joe Grandja - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2TokenRevocationTests { - - private static final String DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI = "/oauth2/revoke"; - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - private static AuthenticationConverter authenticationConverter; - - private static Consumer> authenticationConvertersConsumer; - - private static AuthenticationProvider authenticationProvider; - - private static Consumer> authenticationProvidersConsumer; - - private static AuthenticationSuccessHandler authenticationSuccessHandler; - - private static AuthenticationFailureHandler authenticationFailureHandler; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private OAuth2AuthorizationService authorizationService; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - authenticationConverter = mock(AuthenticationConverter.class); - authenticationConvertersConsumer = mock(Consumer.class); - authenticationProvider = mock(AuthenticationProvider.class); - authenticationProvidersConsumer = mock(Consumer.class); - authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - } - - @AfterEach - public void tearDown() { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenRevokeRefreshTokenThenRevoked() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - OAuth2RefreshToken token = authorization.getRefreshToken().getToken(); - OAuth2TokenType tokenType = OAuth2TokenType.REFRESH_TOKEN; - this.authorizationService.save(authorization); - - this.mvc - .perform(post(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI) - .params(getTokenRevocationRequestParameters(token, tokenType)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()); - - OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); - OAuth2Authorization.Token refreshToken = updatedAuthorization.getRefreshToken(); - assertThat(refreshToken.isInvalidated()).isTrue(); - OAuth2Authorization.Token accessToken = updatedAuthorization.getAccessToken(); - assertThat(accessToken.isInvalidated()).isTrue(); - } - - @Test - public void requestWhenRevokeAccessTokenThenRevoked() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - OAuth2AccessToken token = authorization.getAccessToken().getToken(); - OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN; - this.authorizationService.save(authorization); - - this.mvc - .perform(post(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI) - .params(getTokenRevocationRequestParameters(token, tokenType)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()); - - OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); - OAuth2Authorization.Token accessToken = updatedAuthorization.getAccessToken(); - assertThat(accessToken.isInvalidated()).isTrue(); - OAuth2Authorization.Token refreshToken = updatedAuthorization.getRefreshToken(); - assertThat(refreshToken.isInvalidated()).isFalse(); - } - - @Test - public void requestWhenRevokeAccessTokenAndRequestIncludesIssuerPathThenRevoked() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - OAuth2AccessToken token = authorization.getAccessToken().getToken(); - OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN; - this.authorizationService.save(authorization); - - String issuer = "https://example.com:8443/issuer1"; - - // @formatter:off - this.mvc.perform(post(issuer.concat(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI)) - .params(getTokenRevocationRequestParameters(token, tokenType)) - .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth( - registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()); - // @formatter:on - - OAuth2Authorization updatedAuthorization = this.authorizationService.findById(authorization.getId()); - OAuth2Authorization.Token accessToken = updatedAuthorization.getAccessToken(); - assertThat(accessToken.isInvalidated()).isTrue(); - OAuth2Authorization.Token refreshToken = updatedAuthorization.getRefreshToken(); - assertThat(refreshToken.isInvalidated()).isFalse(); - } - - @Test - public void requestWhenTokenRevocationEndpointCustomizedThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfigurationCustomTokenRevocationEndpoint.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build(); - OAuth2AccessToken token = authorization.getAccessToken().getToken(); - OAuth2TokenType tokenType = OAuth2TokenType.ACCESS_TOKEN; - this.authorizationService.save(authorization); - - Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient, - ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret()); - OAuth2TokenRevocationAuthenticationToken tokenRevocationAuthentication = new OAuth2TokenRevocationAuthenticationToken( - token, clientPrincipal); - - given(authenticationConverter.convert(any())).willReturn(tokenRevocationAuthentication); - given(authenticationProvider.supports(eq(OAuth2TokenRevocationAuthenticationToken.class))).willReturn(true); - given(authenticationProvider.authenticate(any())).willReturn(tokenRevocationAuthentication); - - this.mvc - .perform(post(DEFAULT_TOKEN_REVOCATION_ENDPOINT_URI) - .params(getTokenRevocationRequestParameters(token, tokenType)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()); - - verify(authenticationConverter).convert(any()); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); - List authenticationConverters = authenticationConvertersCaptor.getValue(); - assertThat(authenticationConverters).allMatch((converter) -> converter == authenticationConverter - || converter instanceof OAuth2TokenRevocationAuthenticationConverter); - - verify(authenticationProvider).authenticate(eq(tokenRevocationAuthentication)); - - @SuppressWarnings("unchecked") - ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); - List authenticationProviders = authenticationProvidersCaptor.getValue(); - assertThat(authenticationProviders).allMatch((provider) -> provider == authenticationProvider - || provider instanceof OAuth2TokenRevocationAuthenticationProvider); - - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(tokenRevocationAuthentication)); - } - - private static MultiValueMap getTokenRevocationRequestParameters(OAuth2Token token, - OAuth2TokenType tokenType) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.TOKEN, token.getTokenValue()); - parameters.set(OAuth2ParameterNames.TOKEN_TYPE_HINT, tokenType.getValue()); - return parameters; - } - - private static String encodeBasicAuth(String clientId, String secret) throws Exception { - clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); - secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name()); - String credentialsString = clientId + ":" + secret; - byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); - return new String(encodedBytes, StandardCharsets.UTF_8); - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfiguration { - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); - } - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); - jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); - return jdbcRegisteredClientRepository; - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationCustomTokenRevocationEndpoint - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .tokenRevocationEndpoint((tokenRevocationEndpoint) -> - tokenRevocationEndpoint - .revocationRequestConverter(authenticationConverter) - .revocationRequestConverters(authenticationConvertersConsumer) - .authenticationProvider(authenticationProvider) - .authenticationProviders(authenticationProvidersConsumer) - .revocationResponseHandler(authenticationSuccessHandler) - .errorResponseHandler(authenticationFailureHandler)) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Import(OAuth2AuthorizationServerConfiguration.class) - static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcClientRegistrationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcClientRegistrationTests.java deleted file mode 100644 index df1bd6da849..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcClientRegistrationTests.java +++ /dev/null @@ -1,907 +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.annotation.web.configurers.oauth2.server.authorization; - -import java.time.Duration; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import javax.crypto.spec.SecretKeySpec; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import jakarta.servlet.http.HttpServletResponse; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.assertj.core.data.TemporalUnitWithinOffset; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.server.ServletServerHttpResponse; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.mock.http.MockHttpOutputMessage; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.crypto.factory.PasswordEncoderFactories; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.MacAlgorithm; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.oidc.OidcClientRegistration; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientConfigurationAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.oidc.converter.OidcClientRegistrationRegisteredClientConverter; -import org.springframework.security.oauth2.server.authorization.oidc.converter.RegisteredClientOidcClientRegistrationConverter; -import org.springframework.security.oauth2.server.authorization.oidc.http.converter.OidcClientRegistrationHttpMessageConverter; -import org.springframework.security.oauth2.server.authorization.oidc.web.authentication.OidcClientRegistrationAuthenticationConverter; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.CollectionUtils; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.containsString; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for OpenID Connect Dynamic Client Registration 1.0. - * - * @author Ovidiu Popa - * @author Joe Grandja - * @author Dmitriy Dubson - */ -@ExtendWith(SpringTestContextExtension.class) -public class OidcClientRegistrationTests { - - private static final String ISSUER = "https://example.com:8443/issuer1"; - - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - private static final String DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI = "/connect/register"; - - private static final HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - private static final HttpMessageConverter clientRegistrationHttpMessageConverter = new OidcClientRegistrationHttpMessageConverter(); - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - private static JWKSet clientJwkSet; - - private static JwtEncoder jwtClientAssertionEncoder; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private AuthorizationServerSettings authorizationServerSettings; - - private static AuthenticationConverter authenticationConverter; - - private static Consumer> authenticationConvertersConsumer; - - private static AuthenticationProvider authenticationProvider; - - private static Consumer> authenticationProvidersConsumer; - - private static AuthenticationSuccessHandler authenticationSuccessHandler; - - private static AuthenticationFailureHandler authenticationFailureHandler; - - private MockWebServer server; - - private String clientJwkSetUrl; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - clientJwkSet = new JWKSet(TestJwks.generateRsa().build()); - jwtClientAssertionEncoder = new NimbusJwtEncoder( - (jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet)); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - authenticationConverter = mock(AuthenticationConverter.class); - authenticationConvertersConsumer = mock(Consumer.class); - authenticationProvider = mock(AuthenticationProvider.class); - authenticationProvidersConsumer = mock(Consumer.class); - authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - } - - @BeforeEach - public void setup() throws Exception { - this.server = new MockWebServer(); - this.server.start(); - this.clientJwkSetUrl = this.server.url("/jwks").toString(); - // @formatter:off - MockResponse response = new MockResponse() - .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setBody(clientJwkSet.toString()); - // @formatter:on - this.server.enqueue(response); - given(authenticationProvider.supports(OidcClientRegistrationAuthenticationToken.class)).willReturn(true); - } - - @AfterEach - public void tearDown() throws Exception { - this.server.shutdown(); - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - reset(authenticationConverter); - reset(authenticationConvertersConsumer); - reset(authenticationProvider); - reset(authenticationProvidersConsumer); - reset(authenticationSuccessHandler); - reset(authenticationFailureHandler); - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenClientRegistrationRequestAuthorizedThenClientRegistrationResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - - assertThat(clientRegistrationResponse.getClientId()).isNotNull(); - assertThat(clientRegistrationResponse.getClientIdIssuedAt()).isNotNull(); - assertThat(clientRegistrationResponse.getClientSecret()).isNotNull(); - assertThat(clientRegistrationResponse.getClientSecretExpiresAt()).isNull(); - assertThat(clientRegistrationResponse.getClientName()).isEqualTo(clientRegistration.getClientName()); - assertThat(clientRegistrationResponse.getRedirectUris()) - .containsExactlyInAnyOrderElementsOf(clientRegistration.getRedirectUris()); - assertThat(clientRegistrationResponse.getGrantTypes()) - .containsExactlyInAnyOrderElementsOf(clientRegistration.getGrantTypes()); - assertThat(clientRegistrationResponse.getResponseTypes()) - .containsExactly(OAuth2AuthorizationResponseType.CODE.getValue()); - assertThat(clientRegistrationResponse.getScopes()) - .containsExactlyInAnyOrderElementsOf(clientRegistration.getScopes()); - assertThat(clientRegistrationResponse.getTokenEndpointAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); - assertThat(clientRegistrationResponse.getIdTokenSignedResponseAlgorithm()) - .isEqualTo(SignatureAlgorithm.RS256.getName()); - assertThat(clientRegistrationResponse.getRegistrationClientUrl()).isNotNull(); - assertThat(clientRegistrationResponse.getRegistrationAccessToken()).isNotEmpty(); - } - - @Test - public void requestWhenClientConfigurationRequestAuthorizedThenClientRegistrationResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setBearerAuth(clientRegistrationResponse.getRegistrationAccessToken()); - - MvcResult mvcResult = this.mvc - .perform(get(clientRegistrationResponse.getRegistrationClientUrl().toURI()).headers(httpHeaders)) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andReturn(); - - OidcClientRegistration clientConfigurationResponse = readClientRegistrationResponse(mvcResult.getResponse()); - - assertThat(clientConfigurationResponse.getClientId()).isEqualTo(clientRegistrationResponse.getClientId()); - assertThat(clientConfigurationResponse.getClientIdIssuedAt()) - .isEqualTo(clientRegistrationResponse.getClientIdIssuedAt()); - assertThat(clientConfigurationResponse.getClientSecret()).isNotNull(); - assertThat(clientConfigurationResponse.getClientSecretExpiresAt()) - .isEqualTo(clientRegistrationResponse.getClientSecretExpiresAt()); - assertThat(clientConfigurationResponse.getClientName()).isEqualTo(clientRegistrationResponse.getClientName()); - assertThat(clientConfigurationResponse.getRedirectUris()) - .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getRedirectUris()); - assertThat(clientConfigurationResponse.getGrantTypes()) - .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getGrantTypes()); - assertThat(clientConfigurationResponse.getResponseTypes()) - .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getResponseTypes()); - assertThat(clientConfigurationResponse.getScopes()) - .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getScopes()); - assertThat(clientConfigurationResponse.getTokenEndpointAuthenticationMethod()) - .isEqualTo(clientRegistrationResponse.getTokenEndpointAuthenticationMethod()); - assertThat(clientConfigurationResponse.getIdTokenSignedResponseAlgorithm()) - .isEqualTo(clientRegistrationResponse.getIdTokenSignedResponseAlgorithm()); - assertThat(clientConfigurationResponse.getRegistrationClientUrl()) - .isEqualTo(clientRegistrationResponse.getRegistrationClientUrl()); - assertThat(clientConfigurationResponse.getRegistrationAccessToken()).isNull(); - } - - @Test - public void requestWhenClientRegistrationEndpointCustomizedThenUsed() throws Exception { - this.spring.register(CustomClientRegistrationConfiguration.class).autowire(); - - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - willAnswer((invocation) -> { - HttpServletResponse response = invocation.getArgument(1, HttpServletResponse.class); - ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response); - httpResponse.setStatusCode(HttpStatus.CREATED); - new OidcClientRegistrationHttpMessageConverter().write(clientRegistration, null, httpResponse); - return null; - }).given(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); - - registerClient(clientRegistration); - - verify(authenticationConverter).convert(any()); - ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); - List authenticationConverters = authenticationConvertersCaptor.getValue(); - assertThat(authenticationConverters).hasSize(2) - .allMatch((converter) -> converter == authenticationConverter - || converter instanceof OidcClientRegistrationAuthenticationConverter); - - verify(authenticationProvider).authenticate(any()); - ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); - List authenticationProviders = authenticationProvidersCaptor.getValue(); - assertThat(authenticationProviders).hasSize(3) - .allMatch((provider) -> provider == authenticationProvider - || provider instanceof OidcClientRegistrationAuthenticationProvider - || provider instanceof OidcClientConfigurationAuthenticationProvider); - - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); - verifyNoInteractions(authenticationFailureHandler); - } - - @Test - public void requestWhenClientRegistrationEndpointCustomizedWithAuthenticationFailureHandlerThenUsed() - throws Exception { - this.spring.register(CustomClientRegistrationConfiguration.class).autowire(); - - given(authenticationProvider.authenticate(any())).willThrow(new OAuth2AuthenticationException("error")); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)) - .param(OAuth2ParameterNames.CLIENT_ID, "invalid") - .with(jwt())); - - verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any()); - verifyNoInteractions(authenticationSuccessHandler); - } - - // gh-1056 - @Test - public void requestWhenClientRegistersWithSecretThenClientAuthenticationSuccess() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - - this.mvc - .perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI)) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1") - .with(httpBasic(clientRegistrationResponse.getClientId(), - clientRegistrationResponse.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1")) - .andReturn(); - } - - // gh-1344 - @Test - public void requestWhenClientRegistersWithClientSecretJwtThenClientAuthenticationSuccess() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - - JwsHeader jwsHeader = JwsHeader.with(MacAlgorithm.HS256).build(); - - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); - JwtClaimsSet jwtClaimsSet = JwtClaimsSet.builder() - .issuer(clientRegistrationResponse.getClientId()) - .subject(clientRegistrationResponse.getClientId()) - .audience(Collections.singletonList(asUrl(ISSUER, this.authorizationServerSettings.getTokenEndpoint()))) - .issuedAt(issuedAt) - .expiresAt(expiresAt) - .build(); - - JWKSet jwkSet = new JWKSet( - TestJwks.jwk(new SecretKeySpec(clientRegistrationResponse.getClientSecret().getBytes(), "HS256")) - .build()); - JwtEncoder jwtClientAssertionEncoder = new NimbusJwtEncoder( - (jwkSelector, securityContext) -> jwkSelector.select(jwkSet)); - - Jwt jwtAssertion = jwtClientAssertionEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet)); - - this.mvc - .perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI)) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, "scope1") - .param(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") - .param(OAuth2ParameterNames.CLIENT_ASSERTION, jwtAssertion.getTokenValue()) - .param(OAuth2ParameterNames.CLIENT_ID, clientRegistrationResponse.getClientId())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value("scope1")); - } - - @Test - public void requestWhenClientRegistersWithCustomMetadataThenSavedToRegisteredClient() throws Exception { - this.spring.register(CustomClientMetadataConfiguration.class).autowire(); - - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .claim("custom-metadata-name-1", "value-1") - .claim("custom-metadata-name-2", "value-2") - .claim("non-registered-custom-metadata", "value-3") - .build(); - // @formatter:on - - OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - - RegisteredClient registeredClient = this.registeredClientRepository - .findByClientId(clientRegistrationResponse.getClientId()); - - assertThat(clientRegistrationResponse.getClaim("custom-metadata-name-1")).isEqualTo("value-1"); - assertThat(clientRegistrationResponse.getClaim("custom-metadata-name-2")).isEqualTo("value-2"); - assertThat(clientRegistrationResponse.getClaim("non-registered-custom-metadata")).isNull(); - - assertThat(registeredClient.getClientSettings().getSetting("custom-metadata-name-1")) - .isEqualTo("value-1"); - assertThat(registeredClient.getClientSettings().getSetting("custom-metadata-name-2")) - .isEqualTo("value-2"); - assertThat(registeredClient.getClientSettings().getSetting("non-registered-custom-metadata")).isNull(); - } - - // gh-2111 - @Test - public void requestWhenClientRegistersWithSecretExpirationThenClientRegistrationResponse() throws Exception { - this.spring.register(ClientSecretExpirationConfiguration.class).autowire(); - - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - - OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - - Instant expectedSecretExpiryDate = Instant.now().plus(Duration.ofHours(24)); - TemporalUnitWithinOffset allowedDelta = new TemporalUnitWithinOffset(1, ChronoUnit.MINUTES); - - // Returned response contains expiration date - assertThat(clientRegistrationResponse.getClientSecretExpiresAt()).isNotNull() - .isCloseTo(expectedSecretExpiryDate, allowedDelta); - - RegisteredClient registeredClient = this.registeredClientRepository - .findByClientId(clientRegistrationResponse.getClientId()); - - // Persisted RegisteredClient contains expiration date - assertThat(registeredClient).isNotNull(); - assertThat(registeredClient.getClientSecretExpiresAt()).isNotNull() - .isCloseTo(expectedSecretExpiryDate, allowedDelta); - } - - private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception { - // ***** (1) Obtain the "initial" access token used for registering the client - - String clientRegistrationScope = "client.create"; - // @formatter:off - RegisteredClient clientRegistrar = RegisteredClient.withId("client-registrar-1") - .clientId("client-registrar-1") - .clientAuthenticationMethod(ClientAuthenticationMethod.PRIVATE_KEY_JWT) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .scope(clientRegistrationScope) - .clientSettings( - ClientSettings.builder() - .jwkSetUrl(this.clientJwkSetUrl) - .tokenEndpointAuthenticationSigningAlgorithm(SignatureAlgorithm.RS256) - .build() - ) - .build(); - // @formatter:on - this.registeredClientRepository.save(clientRegistrar); - - // @formatter:off - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.RS256) - .build(); - JwtClaimsSet jwtClaimsSet = jwtClientAssertionClaims(clientRegistrar) - .build(); - // @formatter:on - Jwt jwtAssertion = jwtClientAssertionEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet)); - - MvcResult mvcResult = this.mvc - .perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI)) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, clientRegistrationScope) - .param(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, - "urn:ietf:params:oauth:client-assertion-type:jwt-bearer") - .param(OAuth2ParameterNames.CLIENT_ASSERTION, jwtAssertion.getTokenValue()) - .param(OAuth2ParameterNames.CLIENT_ID, clientRegistrar.getClientId())) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value(clientRegistrationScope)) - .andReturn(); - - OAuth2AccessToken accessToken = readAccessTokenResponse(mvcResult.getResponse()).getAccessToken(); - - // ***** (2) Register the client - - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setBearerAuth(accessToken.getTokenValue()); - - // Register the client - mvcResult = this.mvc - .perform(post(ISSUER.concat(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)).headers(httpHeaders) - .contentType(MediaType.APPLICATION_JSON) - .content(getClientRegistrationRequestContent(clientRegistration))) - .andExpect(status().isCreated()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andReturn(); - - return readClientRegistrationResponse(mvcResult.getResponse()); - } - - private JwtClaimsSet.Builder jwtClientAssertionClaims(RegisteredClient registeredClient) { - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS); - return JwtClaimsSet.builder() - .issuer(registeredClient.getClientId()) - .subject(registeredClient.getClientId()) - .audience(Collections.singletonList(asUrl(ISSUER, this.authorizationServerSettings.getTokenEndpoint()))) - .issuedAt(issuedAt) - .expiresAt(expiresAt); - } - - private static String asUrl(String uri, String path) { - return UriComponentsBuilder.fromUriString(uri).path(path).build().toUriString(); - } - - private static OAuth2AccessTokenResponse readAccessTokenResponse(MockHttpServletResponse response) - throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); - } - - private static byte[] getClientRegistrationRequestContent(OidcClientRegistration clientRegistration) - throws Exception { - MockHttpOutputMessage httpRequest = new MockHttpOutputMessage(); - clientRegistrationHttpMessageConverter.write(clientRegistration, null, httpRequest); - return httpRequest.getBodyAsBytes(); - } - - private static OidcClientRegistration readClientRegistrationResponse(MockHttpServletResponse response) - throws Exception { - MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(), - HttpStatus.valueOf(response.getStatus())); - return clientRegistrationHttpMessageConverter.read(OidcClientRegistration.class, httpResponse); - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class CustomClientRegistrationConfiguration extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - @Override - public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .oidc((oidc) -> - oidc - .clientRegistrationEndpoint((clientRegistration) -> - clientRegistration - .clientRegistrationRequestConverter(authenticationConverter) - .clientRegistrationRequestConverters(authenticationConvertersConsumer) - .authenticationProvider(authenticationProvider) - .authenticationProviders(authenticationProvidersConsumer) - .clientRegistrationResponseHandler(authenticationSuccessHandler) - .errorResponseHandler(authenticationFailureHandler) - ) - ) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class CustomClientMetadataConfiguration extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - @Override - public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .oidc((oidc) -> - oidc - .clientRegistrationEndpoint((clientRegistration) -> - clientRegistration - .authenticationProviders(configureClientRegistrationConverters()) - ) - ) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - private Consumer> configureClientRegistrationConverters() { - // @formatter:off - return (authenticationProviders) -> - authenticationProviders.forEach((authenticationProvider) -> { - List supportedCustomClientMetadata = List.of("custom-metadata-name-1", "custom-metadata-name-2"); - if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider provider) { - provider.setRegisteredClientConverter(new CustomRegisteredClientConverter(supportedCustomClientMetadata)); - provider.setClientRegistrationConverter(new CustomClientRegistrationConverter(supportedCustomClientMetadata)); - } - }); - // @formatter:on - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class ClientSecretExpirationConfiguration extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - @Override - public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .oidc((oidc) -> - oidc - .clientRegistrationEndpoint((clientRegistration) -> - clientRegistration - .authenticationProviders(configureClientRegistrationConverters()) - ) - ) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - private Consumer> configureClientRegistrationConverters() { - // @formatter:off - return (authenticationProviders) -> - authenticationProviders.forEach((authenticationProvider) -> { - if (authenticationProvider instanceof OidcClientRegistrationAuthenticationProvider provider) { - provider.setRegisteredClientConverter(new ClientSecretExpirationRegisteredClientConverter()); - } - }); - // @formatter:on - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .oidc((oidc) -> - oidc - .clientRegistrationEndpoint(Customizer.withDefaults()) - ) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); - JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - registeredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); - registeredClientRepository.save(registeredClient); - return registeredClientRepository; - } - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - JwtDecoder jwtDecoder(JWKSource jwkSource) { - return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); - } - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - @Bean - PasswordEncoder passwordEncoder() { - return PasswordEncoderFactories.createDelegatingPasswordEncoder(); - } - - } - - private static final class CustomRegisteredClientConverter - implements Converter { - - private final OidcClientRegistrationRegisteredClientConverter delegate = new OidcClientRegistrationRegisteredClientConverter(); - - private final List supportedCustomClientMetadata; - - private CustomRegisteredClientConverter(List supportedCustomClientMetadata) { - this.supportedCustomClientMetadata = supportedCustomClientMetadata; - } - - @Override - public RegisteredClient convert(OidcClientRegistration clientRegistration) { - RegisteredClient registeredClient = this.delegate.convert(clientRegistration); - - ClientSettings.Builder clientSettingsBuilder = ClientSettings - .withSettings(registeredClient.getClientSettings().getSettings()); - if (!CollectionUtils.isEmpty(this.supportedCustomClientMetadata)) { - clientRegistration.getClaims().forEach((claim, value) -> { - if (this.supportedCustomClientMetadata.contains(claim)) { - clientSettingsBuilder.setting(claim, value); - } - }); - } - - return RegisteredClient.from(registeredClient).clientSettings(clientSettingsBuilder.build()).build(); - } - - } - - private static final class CustomClientRegistrationConverter - implements Converter { - - private final RegisteredClientOidcClientRegistrationConverter delegate = new RegisteredClientOidcClientRegistrationConverter(); - - private final List supportedCustomClientMetadata; - - private CustomClientRegistrationConverter(List supportedCustomClientMetadata) { - this.supportedCustomClientMetadata = supportedCustomClientMetadata; - } - - @Override - public OidcClientRegistration convert(RegisteredClient registeredClient) { - OidcClientRegistration clientRegistration = this.delegate.convert(registeredClient); - - Map clientMetadata = new HashMap<>(clientRegistration.getClaims()); - if (!CollectionUtils.isEmpty(this.supportedCustomClientMetadata)) { - Map clientSettings = registeredClient.getClientSettings().getSettings(); - this.supportedCustomClientMetadata.forEach((customClaim) -> { - if (clientSettings.containsKey(customClaim)) { - clientMetadata.put(customClaim, clientSettings.get(customClaim)); - } - }); - } - - return OidcClientRegistration.withClaims(clientMetadata).build(); - } - - } - - /** - * This customization adds client secret expiration time by setting - * {@code RegisteredClient.clientSecretExpiresAt} during - * {@code OidcClientRegistration} -> {@code RegisteredClient} conversion - */ - private static final class ClientSecretExpirationRegisteredClientConverter - implements Converter { - - private static final OidcClientRegistrationRegisteredClientConverter delegate = new OidcClientRegistrationRegisteredClientConverter(); - - @Override - public RegisteredClient convert(OidcClientRegistration clientRegistration) { - RegisteredClient registeredClient = delegate.convert(clientRegistration); - RegisteredClient.Builder registeredClientBuilder = RegisteredClient.from(registeredClient); - - Instant clientSecretExpiresAt = Instant.now().plus(Duration.ofHours(24)); - registeredClientBuilder.clientSecretExpiresAt(clientSecretExpiresAt); - - return registeredClientBuilder.build(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcProviderConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcProviderConfigurationTests.java deleted file mode 100644 index 6c33191b53e..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcProviderConfigurationTests.java +++ /dev/null @@ -1,469 +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.annotation.web.configurers.oauth2.server.authorization; - -import java.util.function.Consumer; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.ImmutableJWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationServerMetadataClaimNames; -import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.oidc.OidcProviderConfiguration; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.ResultMatcher; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.hamcrest.CoreMatchers.hasItems; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the OpenID Connect 1.0 Provider Configuration endpoint. - * - * @author Sahariar Alam Khandoker - * @author Joe Grandja - * @author Daniel Garnier-Moiroux - */ -@ExtendWith(SpringTestContextExtension.class) -public class OidcProviderConfigurationTests { - - private static final String DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI = "/.well-known/openid-configuration"; - - private static final String ISSUER = "https://example.com"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private AuthorizationServerSettings authorizationServerSettings; - - @Autowired - private MockMvc mvc; - - @Test - public void requestWhenConfigurationRequestAndIssuerSetThenReturnDefaultConfigurationResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(defaultConfigurationMatchers(ISSUER)); - } - - @Test - public void requestWhenConfigurationRequestIncludesIssuerPathThenConfigurationResponseHasIssuerPath() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire(); - - String issuer = "https://example.com:8443/issuer1"; - this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(defaultConfigurationMatchers(issuer)); - - issuer = "https://example.com:8443/path1/issuer2"; - this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(defaultConfigurationMatchers(issuer)); - - issuer = "https://example.com:8443/path1/path2/issuer3"; - this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(defaultConfigurationMatchers(issuer)); - } - - // gh-632 - @Test - public void requestWhenConfigurationRequestAndUserAuthenticatedThenReturnConfigurationResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)).with(user("user"))) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(defaultConfigurationMatchers(ISSUER)); - } - - // gh-616 - @Test - public void requestWhenConfigurationRequestAndConfigurationCustomizerSetThenReturnCustomConfigurationResponse() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithProviderConfigurationCustomizer.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED, - hasItems(OidcScopes.OPENID, OidcScopes.PROFILE, OidcScopes.EMAIL))); - } - - @Test - public void requestWhenConfigurationRequestAndClientRegistrationEnabledThenConfigurationResponseIncludesRegistrationEndpoint() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithClientRegistrationEnabled.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(defaultConfigurationMatchers(ISSUER)) - .andExpect(jsonPath("$.registration_endpoint") - .value(ISSUER.concat(this.authorizationServerSettings.getOidcClientRegistrationEndpoint()))); - } - - @Test - public void requestWhenConfigurationRequestAndDeviceCodeGrantEnabledThenConfigurationResponseIncludesDeviceAuthorizationEndpoint() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithDeviceCodeGrantEnabled.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(defaultConfigurationMatchers(ISSUER)) - .andExpect(jsonPath("$.device_authorization_endpoint") - .value(ISSUER.concat(this.authorizationServerSettings.getDeviceAuthorizationEndpoint()))) - .andExpect(jsonPath("$.grant_types_supported[4]").value(AuthorizationGrantType.DEVICE_CODE.getValue())); - } - - @Test - public void requestWhenConfigurationRequestAndPushedAuthorizationRequestEnabledThenConfigurationResponseIncludesPushedAuthorizationRequestEndpoint() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled.class).autowire(); - - this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(defaultConfigurationMatchers(ISSUER)) - .andExpect(jsonPath("$.pushed_authorization_request_endpoint") - .value(ISSUER.concat(this.authorizationServerSettings.getPushedAuthorizationRequestEndpoint()))); - } - - private ResultMatcher[] defaultConfigurationMatchers(String issuer) { - // @formatter:off - return new ResultMatcher[] { - jsonPath("issuer").value(issuer), - jsonPath("authorization_endpoint").value(issuer.concat(this.authorizationServerSettings.getAuthorizationEndpoint())), - jsonPath("token_endpoint").value(issuer.concat(this.authorizationServerSettings.getTokenEndpoint())), - jsonPath("$.token_endpoint_auth_methods_supported[0]").value(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()), - jsonPath("$.token_endpoint_auth_methods_supported[1]").value(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()), - jsonPath("$.token_endpoint_auth_methods_supported[2]").value(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()), - jsonPath("$.token_endpoint_auth_methods_supported[3]").value(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()), - jsonPath("jwks_uri").value(issuer.concat(this.authorizationServerSettings.getJwkSetEndpoint())), - jsonPath("userinfo_endpoint").value(issuer.concat(this.authorizationServerSettings.getOidcUserInfoEndpoint())), - jsonPath("end_session_endpoint").value(issuer.concat(this.authorizationServerSettings.getOidcLogoutEndpoint())), - jsonPath("response_types_supported").value(OAuth2AuthorizationResponseType.CODE.getValue()), - jsonPath("$.grant_types_supported[0]").value(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()), - jsonPath("$.grant_types_supported[1]").value(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()), - jsonPath("$.grant_types_supported[2]").value(AuthorizationGrantType.REFRESH_TOKEN.getValue()), - jsonPath("$.grant_types_supported[3]").value(AuthorizationGrantType.TOKEN_EXCHANGE.getValue()), - jsonPath("revocation_endpoint").value(issuer.concat(this.authorizationServerSettings.getTokenRevocationEndpoint())), - jsonPath("$.revocation_endpoint_auth_methods_supported[0]").value(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()), - jsonPath("$.revocation_endpoint_auth_methods_supported[1]").value(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()), - jsonPath("$.revocation_endpoint_auth_methods_supported[2]").value(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()), - jsonPath("$.revocation_endpoint_auth_methods_supported[3]").value(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()), - jsonPath("introspection_endpoint").value(issuer.concat(this.authorizationServerSettings.getTokenIntrospectionEndpoint())), - jsonPath("$.introspection_endpoint_auth_methods_supported[0]").value(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()), - jsonPath("$.introspection_endpoint_auth_methods_supported[1]").value(ClientAuthenticationMethod.CLIENT_SECRET_POST.getValue()), - jsonPath("$.introspection_endpoint_auth_methods_supported[2]").value(ClientAuthenticationMethod.CLIENT_SECRET_JWT.getValue()), - jsonPath("$.introspection_endpoint_auth_methods_supported[3]").value(ClientAuthenticationMethod.PRIVATE_KEY_JWT.getValue()), - jsonPath("$.code_challenge_methods_supported[0]").value("S256"), - jsonPath("subject_types_supported").value("public"), - jsonPath("id_token_signing_alg_values_supported").value(SignatureAlgorithm.RS256.getName()), - jsonPath("scopes_supported").value(OidcScopes.OPENID) - }; - // @formatter:on - } - - @Test - public void loadContextWhenIssuerNotValidUrlThenThrowException() { - assertThatExceptionOfType(BeanCreationException.class).isThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithInvalidIssuerUrl.class).autowire()); - } - - @Test - public void loadContextWhenIssuerNotValidUriThenThrowException() { - assertThatExceptionOfType(BeanCreationException.class).isThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithInvalidIssuerUri.class).autowire()); - } - - @Test - public void loadContextWhenIssuerWithQueryThenThrowException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(AuthorizationServerConfigurationWithIssuerQuery.class).autowire()); - } - - @Test - public void loadContextWhenIssuerWithFragmentThenThrowException() { - assertThatExceptionOfType(BeanCreationException.class).isThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithIssuerFragment.class).autowire()); - } - - @Test - public void loadContextWhenIssuerWithQueryAndFragmentThenThrowException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(AuthorizationServerConfigurationWithIssuerQueryAndFragment.class) - .autowire()); - } - - @Test - public void loadContextWhenIssuerWithEmptyQueryThenThrowException() { - assertThatExceptionOfType(BeanCreationException.class).isThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithIssuerEmptyQuery.class).autowire()); - } - - @Test - public void loadContextWhenIssuerWithEmptyFragmentThenThrowException() { - assertThatExceptionOfType(BeanCreationException.class).isThrownBy( - () -> this.spring.register(AuthorizationServerConfigurationWithIssuerEmptyFragment.class).autowire()); - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfiguration { - - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .oidc(Customizer.withDefaults()) // Enable OpenID Connect 1.0 - ); - // @formatter:on - return http.build(); - } - - @Bean - RegisteredClientRepository registeredClientRepository() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - return new InMemoryRegisteredClientRepository(registeredClient); - } - - @Bean - JWKSource jwkSource() { - return new ImmutableJWKSet<>(new JWKSet(TestJwks.DEFAULT_RSA_JWK)); - } - - @Bean - JwtDecoder jwtDecoder(JWKSource jwkSource) { - return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); - } - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER).build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithProviderConfigurationCustomizer - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .oidc((oidc) -> - oidc.providerConfigurationEndpoint((providerConfigurationEndpoint) -> - providerConfigurationEndpoint - .providerConfigurationCustomizer(providerConfigurationCustomizer()))) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - private Consumer providerConfigurationCustomizer() { - return (providerConfiguration) -> providerConfiguration.scope(OidcScopes.PROFILE).scope(OidcScopes.EMAIL); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithClientRegistrationEnabled - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .oidc((oidc) -> - oidc.clientRegistrationEndpoint(Customizer.withDefaults()) - ) - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithDeviceCodeGrantEnabled extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .deviceAuthorizationEndpoint(Customizer.withDefaults()) - .oidc(Customizer.withDefaults()) - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithPushedAuthorizationRequestEnabled - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .pushedAuthorizationRequestEndpoint(Customizer.withDefaults()) - .oidc(Customizer.withDefaults()) - ); - return http.build(); - } - // @formatter:on - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithInvalidIssuerUrl extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer("urn:example").build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithInvalidIssuerUri extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer("https://not a valid uri").build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithIssuerQuery extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER + "?param=value").build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithIssuerFragment extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER + "#fragment").build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithIssuerQueryAndFragment extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER + "?param=value#fragment").build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithIssuerEmptyQuery extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER + "?").build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithIssuerEmptyFragment extends AuthorizationServerConfiguration { - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().issuer(ISSUER + "#").build(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcTests.java deleted file mode 100644 index 34984b8fa86..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcTests.java +++ /dev/null @@ -1,774 +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.annotation.web.configurers.oauth2.server.authorization; - -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.security.Principal; -import java.util.Base64; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.jdbc.core.JdbcOperations; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder; -import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; -import org.springframework.lang.Nullable; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.core.session.SessionRegistryImpl; -import org.springframework.security.crypto.password.NoOpPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2RefreshToken; -import org.springframework.security.oauth2.core.OAuth2Token; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; -import org.springframework.security.oauth2.server.authorization.token.JwtGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; -import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.util.StringUtils; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.containsString; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for OpenID Connect 1.0. - * - * @author Daniel Garnier-Moiroux - * @author Joe Grandja - */ -@ExtendWith(SpringTestContextExtension.class) -public class OidcTests { - - private static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize"; - - private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token"; - - private static final String DEFAULT_OIDC_LOGOUT_ENDPOINT_URI = "/connect/logout"; - - // See RFC 7636: Appendix B. Example for the S256 code_challenge_method - // https://tools.ietf.org/html/rfc7636#appendix-B - private static final String S256_CODE_VERIFIER = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"; - - private static final String S256_CODE_CHALLENGE = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"; - - private static final String AUTHORITIES_CLAIM = "authorities"; - - private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE); - - private static EmbeddedDatabase db; - - private static JWKSource jwkSource; - - private static HttpMessageConverter accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter(); - - private static SessionRegistry sessionRegistry; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mvc; - - @Autowired - private JdbcOperations jdbcOperations; - - @Autowired - private RegisteredClientRepository registeredClientRepository; - - @Autowired - private OAuth2AuthorizationService authorizationService; - - @Autowired - private JwtDecoder jwtDecoder; - - @Autowired(required = false) - private OAuth2TokenGenerator tokenGenerator; - - @BeforeAll - public static void init() { - JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK); - jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet); - db = new EmbeddedDatabaseBuilder().generateUniqueName(true) - .setType(EmbeddedDatabaseType.HSQL) - .setScriptEncoding("UTF-8") - .addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql") - .addScript( - "org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql") - .build(); - sessionRegistry = spy(new SessionRegistryImpl()); - } - - @AfterEach - public void tearDown() { - if (this.jdbcOperations != null) { - this.jdbcOperations.update("truncate table oauth2_authorization"); - this.jdbcOperations.update("truncate table oauth2_registered_client"); - } - } - - @AfterAll - public static void destroy() { - db.shutdown(); - } - - @Test - public void requestWhenAuthenticationRequestThenTokenResponseIncludesIdToken() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user").roles("A", "B"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=state"); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - mvcResult = this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andExpect(jsonPath("$.id_token").isNotEmpty()) - .andReturn(); - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter - .read(OAuth2AccessTokenResponse.class, httpResponse); - - Jwt idToken = this.jwtDecoder - .decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN)); - - // Assert user authorities was propagated as claim in ID Token - List authoritiesClaim = idToken.getClaim(AUTHORITIES_CLAIM); - Authentication principal = authorization.getAttribute(Principal.class.getName()); - Set userAuthorities = new HashSet<>(); - for (GrantedAuthority authority : principal.getAuthorities()) { - userAuthorities.add(authority.getAuthority()); - } - assertThat(authoritiesClaim).containsExactlyInAnyOrderElementsOf(userAuthorities); - - // Assert sid claim was added in ID Token - assertThat(idToken.getClaim("sid")).isNotNull(); - } - - // gh-1224 - @Test - public void requestWhenRefreshTokenRequestThenIdTokenContainsSidClaim() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user").roles("A", "B"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=state"); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - mvcResult = this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andReturn(); - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter - .read(OAuth2AccessTokenResponse.class, httpResponse); - - Jwt idToken = this.jwtDecoder - .decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN)); - - String sidClaim = idToken.getClaim("sid"); - assertThat(sidClaim).isNotNull(); - - // Refresh access token - mvcResult = this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.REFRESH_TOKEN.getValue()) - .param(OAuth2ParameterNames.REFRESH_TOKEN, accessTokenResponse.getRefreshToken().getTokenValue()) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andReturn(); - - servletResponse = mvcResult.getResponse(); - httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - accessTokenResponse = accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); - - idToken = this.jwtDecoder - .decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN)); - - assertThat(idToken.getClaim("sid")).isEqualTo(sidClaim); - } - - @Test - public void requestWhenLogoutRequestThenLogout() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - this.registeredClientRepository.save(registeredClient); - - String issuer = "https://example.com:8443/issuer1"; - - // Login - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - MvcResult mvcResult = this.mvc - .perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI)).queryParams(authorizationRequestParameters) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - - MockHttpSession session = (MockHttpSession) mvcResult.getRequest().getSession(); - assertThat(session.isNew()).isTrue(); - - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - // Get ID Token - mvcResult = this.mvc - .perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI)) - .params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andReturn(); - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter - .read(OAuth2AccessTokenResponse.class, httpResponse); - - String idToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN); - - // Logout - mvcResult = this.mvc - .perform(post(issuer.concat(DEFAULT_OIDC_LOGOUT_ENDPOINT_URI)).param("id_token_hint", idToken) - .session(session)) - .andExpect(status().is3xxRedirection()) - .andReturn(); - redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - - assertThat(redirectedUrl).matches("/"); - assertThat(session.isInvalid()).isTrue(); - } - - @Test - public void requestWhenLogoutRequestWithOtherUsersIdTokenThenNotLogout() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // Login user1 - RegisteredClient registeredClient1 = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - this.registeredClientRepository.save(registeredClient1); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient1); - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user1"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - - MockHttpSession user1Session = (MockHttpSession) mvcResult.getRequest().getSession(); - assertThat(user1Session.isNew()).isTrue(); - - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization user1Authorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - mvcResult = this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient1, user1Authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient1.getClientId(), - registeredClient1.getClientSecret()))) - .andExpect(status().isOk()) - .andReturn(); - - MockHttpServletResponse servletResponse = mvcResult.getResponse(); - MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - OAuth2AccessTokenResponse accessTokenResponse = accessTokenHttpResponseConverter - .read(OAuth2AccessTokenResponse.class, httpResponse); - - String user1IdToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN); - - // Login user2 - RegisteredClient registeredClient2 = TestRegisteredClients.registeredClient2().scope(OidcScopes.OPENID).build(); - this.registeredClientRepository.save(registeredClient2); - - authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient2); - mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user2"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - - MockHttpSession user2Session = (MockHttpSession) mvcResult.getRequest().getSession(); - assertThat(user2Session.isNew()).isTrue(); - - redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization user2Authorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - mvcResult = this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .params(getTokenRequestParameters(registeredClient2, user2Authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient2.getClientId(), - registeredClient2.getClientSecret()))) - .andExpect(status().isOk()) - .andReturn(); - - servletResponse = mvcResult.getResponse(); - httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(), - HttpStatus.valueOf(servletResponse.getStatus())); - accessTokenResponse = accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse); - - String user2IdToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN); - - // Attempt to log out user1 using user2's ID Token - mvcResult = this.mvc - .perform(post(DEFAULT_OIDC_LOGOUT_ENDPOINT_URI).param("id_token_hint", user2IdToken).session(user1Session)) - .andExpect(status().isBadRequest()) - .andExpect(status().reason("[invalid_token] OpenID Connect 1.0 Logout Request Parameter: sub")) - .andReturn(); - - assertThat(user1Session.isInvalid()).isFalse(); - } - - @Test - public void requestWhenCustomTokenGeneratorThenUsed() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithTokenGenerator.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - this.registeredClientRepository.save(registeredClient); - - OAuth2Authorization authorization = createAuthorization(registeredClient); - this.authorizationService.save(authorization); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()); - - verify(this.tokenGenerator, times(3)).generate(any()); - } - - // gh-1422 - @Test - public void requestWhenAuthenticationRequestWithOfflineAccessScopeThenTokenResponseIncludesRefreshToken() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithCustomRefreshTokenGenerator.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .scope(OidcScopes.OPENID) - .scope("offline_access") - .build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=state"); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andExpect(jsonPath("$.id_token").isNotEmpty()) - .andReturn(); - } - - // gh-1422 - @Test - public void requestWhenAuthenticationRequestWithoutOfflineAccessScopeThenTokenResponseDoesNotIncludeRefreshToken() - throws Exception { - this.spring.register(AuthorizationServerConfigurationWithCustomRefreshTokenGenerator.class).autowire(); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build(); - this.registeredClientRepository.save(registeredClient); - - MultiValueMap authorizationRequestParameters = getAuthorizationRequestParameters( - registeredClient); - MvcResult mvcResult = this.mvc - .perform(get(DEFAULT_AUTHORIZATION_ENDPOINT_URI).queryParams(authorizationRequestParameters) - .with(user("user"))) - .andExpect(status().is3xxRedirection()) - .andReturn(); - String redirectedUrl = mvcResult.getResponse().getRedirectedUrl(); - String expectedRedirectUri = authorizationRequestParameters.getFirst(OAuth2ParameterNames.REDIRECT_URI); - assertThat(redirectedUrl).matches(expectedRedirectUri + "\\?code=.{15,}&state=state"); - - String authorizationCode = extractParameterFromRedirectUri(redirectedUrl, "code"); - OAuth2Authorization authorization = this.authorizationService.findByToken(authorizationCode, - AUTHORIZATION_CODE_TOKEN_TYPE); - - this.mvc - .perform(post(DEFAULT_TOKEN_ENDPOINT_URI).params(getTokenRequestParameters(registeredClient, authorization)) - .header(HttpHeaders.AUTHORIZATION, - "Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.token_type").isNotEmpty()) - .andExpect(jsonPath("$.expires_in").isNotEmpty()) - .andExpect(jsonPath("$.refresh_token").doesNotExist()) - .andExpect(jsonPath("$.scope").isNotEmpty()) - .andExpect(jsonPath("$.id_token").isNotEmpty()) - .andReturn(); - } - - private static OAuth2Authorization createAuthorization(RegisteredClient registeredClient) { - Map additionalParameters = new HashMap<>(); - additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE); - additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); - return TestOAuth2Authorizations.authorization(registeredClient, additionalParameters).build(); - } - - private static MultiValueMap getAuthorizationRequestParameters(RegisteredClient registeredClient) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue()); - parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()); - parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); - parameters.set(OAuth2ParameterNames.SCOPE, - StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " ")); - parameters.set(OAuth2ParameterNames.STATE, "state"); - parameters.set(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE); - parameters.set(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256"); - return parameters; - } - - private static MultiValueMap getTokenRequestParameters(RegisteredClient registeredClient, - OAuth2Authorization authorization) { - MultiValueMap parameters = new LinkedMultiValueMap<>(); - parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()); - parameters.set(OAuth2ParameterNames.CODE, - authorization.getToken(OAuth2AuthorizationCode.class).getToken().getTokenValue()); - parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next()); - parameters.set(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER); - return parameters; - } - - private static String encodeBasicAuth(String clientId, String secret) throws Exception { - clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); - secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name()); - String credentialsString = clientId + ":" + secret; - byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8)); - return new String(encodedBytes, StandardCharsets.UTF_8); - } - - private String extractParameterFromRedirectUri(String redirectUri, String param) - throws UnsupportedEncodingException { - String locationHeader = URLDecoder.decode(redirectUri, StandardCharsets.UTF_8.name()); - UriComponents uriComponents = UriComponentsBuilder.fromUriString(locationHeader).build(); - return uriComponents.getQueryParams().getFirst(param); - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfiguration { - - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .oidc(Customizer.withDefaults()) // Enable OpenID Connect 1.0 - ); - // @formatter:on - return http.build(); - } - - @Bean - OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations, - RegisteredClientRepository registeredClientRepository) { - return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository); - } - - @Bean - RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) { - JdbcRegisteredClientRepository jdbcRegisteredClientRepository = new JdbcRegisteredClientRepository( - jdbcOperations); - RegisteredClientParametersMapper registeredClientParametersMapper = new RegisteredClientParametersMapper(); - jdbcRegisteredClientRepository.setRegisteredClientParametersMapper(registeredClientParametersMapper); - return jdbcRegisteredClientRepository; - } - - @Bean - JdbcOperations jdbcOperations() { - return new JdbcTemplate(db); - } - - @Bean - JWKSource jwkSource() { - return jwkSource; - } - - @Bean - JwtDecoder jwtDecoder(JWKSource jwkSource) { - return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); - } - - @Bean - OAuth2TokenCustomizer jwtCustomizer() { - return (context) -> { - if (context.getTokenType().getValue().equals(OidcParameterNames.ID_TOKEN)) { - Authentication principal = context.getPrincipal(); - Set authorities = new HashSet<>(); - for (GrantedAuthority authority : principal.getAuthorities()) { - authorities.add(authority.getAuthority()); - } - context.getClaims().claim(AUTHORITIES_CLAIM, authorities); - } - }; - } - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - @Bean - PasswordEncoder passwordEncoder() { - return NoOpPasswordEncoder.getInstance(); - } - - @Bean - SessionRegistry sessionRegistry() { - return sessionRegistry; - } - - } - - @EnableWebSecurity - @Configuration - static class AuthorizationServerConfigurationWithTokenGenerator extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .tokenGenerator(tokenGenerator()) - .oidc(Customizer.withDefaults()) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - @Bean - OAuth2TokenGenerator tokenGenerator() { - JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource())); - jwtGenerator.setJwtCustomizer(jwtCustomizer()); - OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator(); - OAuth2TokenGenerator delegatingTokenGenerator = new DelegatingOAuth2TokenGenerator( - jwtGenerator, refreshTokenGenerator); - return spy(new OAuth2TokenGenerator() { - @Override - public OAuth2Token generate(OAuth2TokenContext context) { - return delegatingTokenGenerator.generate(context); - } - }); - } - - } - - @EnableWebSecurity - @Configuration - static class AuthorizationServerConfigurationWithCustomRefreshTokenGenerator - extends AuthorizationServerConfiguration { - - // @formatter:off - @Bean - SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .tokenGenerator(tokenGenerator()) - .oidc(Customizer.withDefaults()) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - return http.build(); - } - // @formatter:on - - @Bean - OAuth2TokenGenerator tokenGenerator() { - JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource())); - jwtGenerator.setJwtCustomizer(jwtCustomizer()); - OAuth2TokenGenerator refreshTokenGenerator = new CustomRefreshTokenGenerator(); - return new DelegatingOAuth2TokenGenerator(jwtGenerator, refreshTokenGenerator); - } - - private static final class CustomRefreshTokenGenerator implements OAuth2TokenGenerator { - - private final OAuth2RefreshTokenGenerator delegate = new OAuth2RefreshTokenGenerator(); - - @Nullable - @Override - public OAuth2RefreshToken generate(OAuth2TokenContext context) { - if (context.getAuthorizedScopes().contains(OidcScopes.OPENID) - && !context.getAuthorizedScopes().contains("offline_access")) { - return null; - } - return this.delegate.generate(context); - } - - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcUserInfoTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcUserInfoTests.java deleted file mode 100644 index 54195bdf903..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcUserInfoTests.java +++ /dev/null @@ -1,512 +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.annotation.web.configurers.oauth2.server.authorization; - -import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Function; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.source.ImmutableJWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.OidcScopes; -import org.springframework.security.oauth2.core.oidc.OidcUserInfo; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; -import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; -import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations; -import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; -import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; -import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationProvider; -import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken; -import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.ResultMatcher; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for the OpenID Connect 1.0 UserInfo endpoint. - * - * @author Steve Riesenberg - */ -@ExtendWith(SpringTestContextExtension.class) -public class OidcUserInfoTests { - - private static final String DEFAULT_OIDC_USER_INFO_ENDPOINT_URI = "/userinfo"; - - private static SecurityContextRepository securityContextRepository; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mvc; - - @Autowired - private JwtEncoder jwtEncoder; - - @Autowired - private JwtDecoder jwtDecoder; - - @Autowired - private OAuth2AuthorizationService authorizationService; - - private static AuthenticationConverter authenticationConverter; - - private static Consumer> authenticationConvertersConsumer; - - private static AuthenticationProvider authenticationProvider; - - private static Consumer> authenticationProvidersConsumer; - - private static AuthenticationSuccessHandler authenticationSuccessHandler; - - private static AuthenticationFailureHandler authenticationFailureHandler; - - private static Function userInfoMapper; - - @BeforeAll - public static void init() { - securityContextRepository = spy(new HttpSessionSecurityContextRepository()); - authenticationConverter = mock(AuthenticationConverter.class); - authenticationConvertersConsumer = mock(Consumer.class); - authenticationProvider = mock(AuthenticationProvider.class); - authenticationProvidersConsumer = mock(Consumer.class); - authenticationSuccessHandler = mock(AuthenticationSuccessHandler.class); - authenticationFailureHandler = mock(AuthenticationFailureHandler.class); - userInfoMapper = mock(Function.class); - } - - @BeforeEach - public void setup() { - reset(securityContextRepository); - reset(authenticationConverter); - reset(authenticationConvertersConsumer); - reset(authenticationProvider); - reset(authenticationProvidersConsumer); - reset(authenticationSuccessHandler); - reset(authenticationFailureHandler); - reset(userInfoMapper); - } - - @Test - public void requestWhenUserInfoRequestGetThenUserInfoResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - OAuth2Authorization authorization = createAuthorization(); - this.authorizationService.save(authorization); - - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - // @formatter:off - this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(userInfoResponse()); - // @formatter:on - } - - @Test - public void requestWhenUserInfoRequestPostThenUserInfoResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - OAuth2Authorization authorization = createAuthorization(); - this.authorizationService.save(authorization); - - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - // @formatter:off - this.mvc.perform(post(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(userInfoResponse()); - // @formatter:on - } - - @Test - public void requestWhenUserInfoRequestIncludesIssuerPathThenUserInfoResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - OAuth2Authorization authorization = createAuthorization(); - this.authorizationService.save(authorization); - - String issuer = "https://example.com:8443/issuer1"; - - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - // @formatter:off - this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI)) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(userInfoResponse()); - // @formatter:on - } - - @Test - public void requestWhenUserInfoEndpointCustomizedThenUsed() throws Exception { - this.spring.register(CustomUserInfoConfiguration.class).autowire(); - - OAuth2Authorization authorization = createAuthorization(); - this.authorizationService.save(authorization); - - given(userInfoMapper.apply(any())).willReturn(createUserInfo()); - - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - // @formatter:off - this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) - .andExpect(status().is2xxSuccessful()); - // @formatter:on - - verify(userInfoMapper).apply(any()); - verify(authenticationConverter).convert(any()); - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); - verifyNoInteractions(authenticationFailureHandler); - - ArgumentCaptor> authenticationProvidersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture()); - List authenticationProviders = authenticationProvidersCaptor.getValue(); - assertThat(authenticationProviders).hasSize(2) - .allMatch((provider) -> provider == authenticationProvider - || provider instanceof OidcUserInfoAuthenticationProvider); - - ArgumentCaptor> authenticationConvertersCaptor = ArgumentCaptor - .forClass(List.class); - verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture()); - List authenticationConverters = authenticationConvertersCaptor.getValue(); - assertThat(authenticationConverters).hasSize(2).allMatch(AuthenticationConverter.class::isInstance); - } - - @Test - public void requestWhenUserInfoEndpointCustomizedWithAuthenticationProviderThenUsed() throws Exception { - this.spring.register(CustomUserInfoConfiguration.class).autowire(); - - OAuth2Authorization authorization = createAuthorization(); - this.authorizationService.save(authorization); - - given(authenticationProvider.supports(eq(OidcUserInfoAuthenticationToken.class))).willReturn(true); - String tokenValue = authorization.getAccessToken().getToken().getTokenValue(); - Jwt jwt = this.jwtDecoder.decode(tokenValue); - OidcUserInfoAuthenticationToken oidcUserInfoAuthentication = new OidcUserInfoAuthenticationToken( - new JwtAuthenticationToken(jwt), createUserInfo()); - given(authenticationProvider.authenticate(any())).willReturn(oidcUserInfoAuthentication); - - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - // @formatter:off - this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) - .andExpect(status().is2xxSuccessful()); - // @formatter:on - - verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), any()); - verify(authenticationProvider).authenticate(any()); - verifyNoInteractions(authenticationFailureHandler); - verifyNoInteractions(userInfoMapper); - } - - @Test - public void requestWhenUserInfoEndpointCustomizedWithAuthenticationFailureHandlerThenUsed() throws Exception { - this.spring.register(CustomUserInfoConfiguration.class).autowire(); - - given(userInfoMapper.apply(any())).willReturn(createUserInfo()); - willAnswer((invocation) -> { - HttpServletResponse response = invocation.getArgument(1); - response.setStatus(HttpStatus.UNAUTHORIZED.value()); - response.getWriter().write("unauthorized"); - return null; - }).given(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any()); - - OAuth2AccessToken accessToken = createAuthorization().getAccessToken().getToken(); - // @formatter:off - this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) - .andExpect(status().is4xxClientError()); - // @formatter:on - - verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any()); - verifyNoInteractions(authenticationSuccessHandler); - verifyNoInteractions(userInfoMapper); - } - - // gh-482 - @Test - public void requestWhenUserInfoRequestThenBearerTokenAuthenticationNotPersisted() throws Exception { - this.spring.register(AuthorizationServerConfigurationWithSecurityContextRepository.class).autowire(); - - OAuth2Authorization authorization = createAuthorization(); - this.authorizationService.save(authorization); - - OAuth2AccessToken accessToken = authorization.getAccessToken().getToken(); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get(DEFAULT_OIDC_USER_INFO_ENDPOINT_URI) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken.getTokenValue())) - .andExpect(status().is2xxSuccessful()) - .andExpectAll(userInfoResponse()) - .andReturn(); - // @formatter:on - - org.springframework.security.core.context.SecurityContext securityContext = securityContextRepository - .loadDeferredContext(mvcResult.getRequest()) - .get(); - assertThat(securityContext.getAuthentication()).isNull(); - } - - private static ResultMatcher[] userInfoResponse() { - // @formatter:off - return new ResultMatcher[] { - jsonPath("sub").value("user1"), - jsonPath("name").value("First Last"), - jsonPath("given_name").value("First"), - jsonPath("family_name").value("Last"), - jsonPath("middle_name").value("Middle"), - jsonPath("nickname").value("User"), - jsonPath("preferred_username").value("user"), - jsonPath("profile").value("https://example.com/user1"), - jsonPath("picture").value("https://example.com/user1.jpg"), - jsonPath("website").value("https://example.com"), - jsonPath("email").value("user1@example.com"), - jsonPath("email_verified").value("true"), - jsonPath("gender").value("female"), - jsonPath("birthdate").value("1970-01-01"), - jsonPath("zoneinfo").value("Europe/Paris"), - jsonPath("locale").value("en-US"), - jsonPath("phone_number").value("+1 (604) 555-1234;ext=5678"), - jsonPath("phone_number_verified").value("false"), - jsonPath("address.formatted").value("Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance"), - jsonPath("updated_at").value("1970-01-01T00:00:00Z") - }; - // @formatter:on - } - - private OAuth2Authorization createAuthorization() { - JwsHeader headers = JwsHeader.with(SignatureAlgorithm.RS256).build(); - // @formatter:off - JwtClaimsSet claimSet = JwtClaimsSet.builder() - .claims((claims) -> claims.putAll(createUserInfo().getClaims())) - .build(); - // @formatter:on - Jwt jwt = this.jwtEncoder.encode(JwtEncoderParameters.from(headers, claimSet)); - - Instant now = Instant.now(); - Set scopes = new HashSet<>(Arrays.asList(OidcScopes.OPENID, OidcScopes.ADDRESS, OidcScopes.EMAIL, - OidcScopes.PHONE, OidcScopes.PROFILE)); - OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), - now, now.plusSeconds(300), scopes); - OidcIdToken idToken = OidcIdToken.withTokenValue("id-token") - .claims((claims) -> claims.putAll(createUserInfo().getClaims())) - .build(); - - return TestOAuth2Authorizations.authorization().accessToken(accessToken).token(idToken).build(); - } - - private static OidcUserInfo createUserInfo() { - // @formatter:off - return OidcUserInfo.builder() - .subject("user1") - .name("First Last") - .givenName("First") - .familyName("Last") - .middleName("Middle") - .nickname("User") - .preferredUsername("user") - .profile("https://example.com/user1") - .picture("https://example.com/user1.jpg") - .website("https://example.com") - .email("user1@example.com") - .emailVerified(true) - .gender("female") - .birthdate("1970-01-01") - .zoneinfo("Europe/Paris") - .locale("en-US") - .phoneNumber("+1 (604) 555-1234;ext=5678") - .phoneNumberVerified(false) - .claim("address", Collections.singletonMap("formatted", "Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance")) - .updatedAt("1970-01-01T00:00:00Z") - .build(); - // @formatter:on - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class CustomUserInfoConfiguration extends AuthorizationServerConfiguration { - - @Bean - @Override - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .oidc((oidc) -> - oidc - .userInfoEndpoint((userInfo) -> - userInfo - .userInfoRequestConverter(authenticationConverter) - .userInfoRequestConverters(authenticationConvertersConsumer) - .authenticationProvider(authenticationProvider) - .authenticationProviders(authenticationProvidersConsumer) - .userInfoResponseHandler(authenticationSuccessHandler) - .errorResponseHandler(authenticationFailureHandler) - .userInfoMapper(userInfoMapper))) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - // @formatter:on - return http.build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfigurationWithSecurityContextRepository - extends AuthorizationServerConfiguration { - - @Bean - @Override - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .oidc(Customizer.withDefaults()) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ) - .securityContext((securityContext) -> - securityContext.securityContextRepository(securityContextRepository)); - // @formatter:on - - return http.build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class AuthorizationServerConfiguration { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2AuthorizationServer((authorizationServer) -> - authorizationServer - .oidc(Customizer.withDefaults()) - ) - .authorizeHttpRequests((authorize) -> - authorize.anyRequest().authenticated() - ); - // @formatter:on - - return http.build(); - } - - @Bean - RegisteredClientRepository registeredClientRepository() { - RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); - return new InMemoryRegisteredClientRepository(registeredClient); - } - - @Bean - OAuth2AuthorizationService authorizationService() { - return new InMemoryOAuth2AuthorizationService(); - } - - @Bean - JWKSource jwkSource() { - return new ImmutableJWKSet<>(new JWKSet(TestJwks.DEFAULT_RSA_JWK)); - } - - @Bean - JwtDecoder jwtDecoder(JWKSource jwkSource) { - return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); - } - - @Bean - JwtEncoder jwtEncoder(JWKSource jwkSource) { - return new NimbusJwtEncoder(jwkSource); - } - - @Bean - AuthorizationServerSettings authorizationServerSettings() { - return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/DPoPAuthenticationConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/DPoPAuthenticationConfigurerTests.java deleted file mode 100644 index e597406c0d6..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/DPoPAuthenticationConfigurerTests.java +++ /dev/null @@ -1,276 +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.annotation.web.configurers.oauth2.server.resource; - -import java.nio.charset.StandardCharsets; -import java.security.MessageDigest; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Base64; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.UUID; - -import com.nimbusds.jose.jwk.ECKey; -import com.nimbusds.jose.jwk.JWK; -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.jose.TestJwks; -import org.springframework.security.oauth2.jose.TestKeys; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link DPoPAuthenticationConfigurer}. - * - * @author Joe Grandja - */ -@ExtendWith(SpringTestContextExtension.class) -public class DPoPAuthenticationConfigurerTests { - - private static final RSAPublicKey PROVIDER_RSA_PUBLIC_KEY = TestKeys.DEFAULT_PUBLIC_KEY; - - private static final RSAPrivateKey PROVIDER_RSA_PRIVATE_KEY = TestKeys.DEFAULT_PRIVATE_KEY; - - private static final ECPublicKey CLIENT_EC_PUBLIC_KEY = (ECPublicKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPublic(); - - private static final ECPrivateKey CLIENT_EC_PRIVATE_KEY = (ECPrivateKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPrivate(); - - private static final ECKey CLIENT_EC_KEY = TestJwks.jwk(CLIENT_EC_PUBLIC_KEY, CLIENT_EC_PRIVATE_KEY).build(); - - private static NimbusJwtEncoder providerJwtEncoder; - - private static NimbusJwtEncoder clientJwtEncoder; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mvc; - - @BeforeAll - public static void init() { - RSAKey providerRsaKey = TestJwks.jwk(PROVIDER_RSA_PUBLIC_KEY, PROVIDER_RSA_PRIVATE_KEY).build(); - JWKSource providerJwkSource = (jwkSelector, securityContext) -> jwkSelector - .select(new JWKSet(providerRsaKey)); - providerJwtEncoder = new NimbusJwtEncoder(providerJwkSource); - JWKSource clientJwkSource = (jwkSelector, securityContext) -> jwkSelector - .select(new JWKSet(CLIENT_EC_KEY)); - clientJwtEncoder = new NimbusJwtEncoder(clientJwkSource); - } - - @Test - public void requestWhenDPoPAndBearerAuthenticationThenUnauthorized() throws Exception { - this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); - Set scope = Collections.singleton("resource1.read"); - String accessToken = generateAccessToken(scope, CLIENT_EC_KEY); - String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); - // @formatter:off - this.mvc.perform(get("/resource1") - .header(HttpHeaders.AUTHORIZATION, "DPoP " + accessToken) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) - .header("DPoP", dPoPProof)) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, - "DPoP error=\"invalid_request\", error_description=\"Found multiple Authorization headers.\", algs=\"RS256 RS384 RS512 PS256 PS384 PS512 ES256 ES384 ES512\"")); - // @formatter:on - } - - @Test - public void requestWhenDPoPAccessTokenMalformedThenUnauthorized() throws Exception { - this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); - Set scope = Collections.singleton("resource1.read"); - String accessToken = generateAccessToken(scope, CLIENT_EC_KEY); - String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); - // @formatter:off - this.mvc.perform(get("/resource1") - .header(HttpHeaders.AUTHORIZATION, "DPoP " + accessToken + " m a l f o r m e d ") - .header("DPoP", dPoPProof)) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, - "DPoP error=\"invalid_token\", error_description=\"DPoP access token is malformed.\", algs=\"RS256 RS384 RS512 PS256 PS384 PS512 ES256 ES384 ES512\"")); - // @formatter:on - } - - @Test - public void requestWhenMultipleDPoPProofsThenUnauthorized() throws Exception { - this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); - Set scope = Collections.singleton("resource1.read"); - String accessToken = generateAccessToken(scope, CLIENT_EC_KEY); - String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); - // @formatter:off - this.mvc.perform(get("/resource1") - .header(HttpHeaders.AUTHORIZATION, "DPoP " + accessToken) - .header("DPoP", dPoPProof) - .header("DPoP", dPoPProof)) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, - "DPoP error=\"invalid_request\", error_description=\"DPoP proof is missing or invalid.\", algs=\"RS256 RS384 RS512 PS256 PS384 PS512 ES256 ES384 ES512\"")); - // @formatter:on - } - - @Test - public void requestWhenDPoPAuthenticationValidThenAccessed() throws Exception { - this.spring.register(SecurityConfig.class, ResourceEndpoints.class).autowire(); - Set scope = Collections.singleton("resource1.read"); - String accessToken = generateAccessToken(scope, CLIENT_EC_KEY); - String dPoPProof = generateDPoPProof(HttpMethod.GET.name(), "http://localhost/resource1", accessToken); - // @formatter:off - this.mvc.perform(get("/resource1") - .header(HttpHeaders.AUTHORIZATION, "DPoP " + accessToken) - .header("DPoP", dPoPProof)) - .andExpect(status().isOk()) - .andExpect(content().string("resource1")); - // @formatter:on - } - - private static String generateAccessToken(Set scope, JWK jwk) { - Map jktClaim = null; - if (jwk != null) { - try { - String sha256Thumbprint = jwk.toPublicJWK().computeThumbprint().toString(); - jktClaim = new HashMap<>(); - jktClaim.put("jkt", sha256Thumbprint); - } - catch (Exception ignored) { - } - } - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.RS256).build(); - Instant issuedAt = Instant.now(); - Instant expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES); - // @formatter:off - JwtClaimsSet.Builder claimsBuilder = JwtClaimsSet.builder() - .issuer("https://provider.com") - .subject("subject") - .issuedAt(issuedAt) - .expiresAt(expiresAt) - .id(UUID.randomUUID().toString()) - .claim(OAuth2ParameterNames.SCOPE, scope); - if (jktClaim != null) { - claimsBuilder.claim("cnf", jktClaim); // Bind client public key - } - // @formatter:on - Jwt jwt = providerJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claimsBuilder.build())); - return jwt.getTokenValue(); - } - - private static String generateDPoPProof(String method, String resourceUri, String accessToken) throws Exception { - // @formatter:off - Map publicJwk = CLIENT_EC_KEY.toPublicJWK().toJSONObject(); - JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.ES256) - .type("dpop+jwt") - .jwk(publicJwk) - .build(); - JwtClaimsSet claims = JwtClaimsSet.builder() - .issuedAt(Instant.now()) - .claim("htm", method) - .claim("htu", resourceUri) - .claim("ath", computeSHA256(accessToken)) - .id(UUID.randomUUID().toString()) - .build(); - // @formatter:on - Jwt jwt = clientJwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)); - return jwt.getTokenValue(); - } - - private static String computeSHA256(String value) throws Exception { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - byte[] digest = md.digest(value.getBytes(StandardCharsets.UTF_8)); - return Base64.getUrlEncoder().withoutPadding().encodeToString(digest); - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class SecurityConfig { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .requestMatchers("/resource1").hasAnyAuthority("SCOPE_resource1.read", "SCOPE_resource1.write") - .requestMatchers("/resource2").hasAnyAuthority("SCOPE_resource2.read", "SCOPE_resource2.write") - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2) -> oauth2 - .jwt(Customizer.withDefaults())); - // @formatter:on - return http.build(); - } - - @Bean - NimbusJwtDecoder jwtDecoder() { - return NimbusJwtDecoder.withPublicKey(PROVIDER_RSA_PUBLIC_KEY).build(); - } - - } - - @RestController - static class ResourceEndpoints { - - @RequestMapping(value = "/resource1", method = { RequestMethod.GET, RequestMethod.POST }) - String resource1() { - return "resource1"; - } - - @RequestMapping(value = "/resource2", method = { RequestMethod.GET, RequestMethod.POST }) - String resource2() { - return "resource2"; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ProtectedResourceMetadataTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ProtectedResourceMetadataTests.java deleted file mode 100644 index 718b8ccee42..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ProtectedResourceMetadataTests.java +++ /dev/null @@ -1,200 +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.annotation.web.configurers.oauth2.server.resource; - -import java.util.function.Consumer; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.oauth2.jose.TestKeys; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; -import org.springframework.security.oauth2.server.resource.OAuth2ProtectedResourceMetadata; -import org.springframework.security.oauth2.server.resource.OAuth2ProtectedResourceMetadataClaimNames; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; - -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.hasSize; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Integration tests for OAuth 2.0 Protected Resource Metadata Requests. - * - * @author Joe Grandja - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2ProtectedResourceMetadataTests { - - private static final String DEFAULT_OAUTH2_PROTECTED_RESOURCE_METADATA_ENDPOINT_URI = "/.well-known/oauth-protected-resource"; - - private static final String RESOURCE = "https://resource.com:8443"; - - private static final String ISSUER_1 = "https://provider1.com"; - - private static final String ISSUER_2 = "https://provider2.com"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mvc; - - @Test - public void requestWhenProtectedResourceMetadataRequestThenReturnMetadataResponse() throws Exception { - this.spring.register(ResourceServerConfiguration.class).autowire(); - - this.mvc.perform(get(RESOURCE.concat(DEFAULT_OAUTH2_PROTECTED_RESOURCE_METADATA_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.RESOURCE).value(RESOURCE)) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.BEARER_METHODS_SUPPORTED).isArray()) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.BEARER_METHODS_SUPPORTED).value(hasSize(1))) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.BEARER_METHODS_SUPPORTED) - .value(hasItem("header"))) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.TLS_CLIENT_CERTIFICATE_BOUND_ACCESS_TOKENS) - .value(true)) - .andReturn(); - } - - @Test - public void requestWhenProtectedResourceMetadataRequestIncludesResourcePathThenMetadataResponseHasResourcePath() - throws Exception { - this.spring.register(ResourceServerConfiguration.class).autowire(); - - String host = RESOURCE; - - String resourcePath = "/resource1"; - String resource = host.concat(resourcePath); - this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_PROTECTED_RESOURCE_METADATA_ENDPOINT_URI).concat(resourcePath))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.RESOURCE).value(resource)) - .andReturn(); - - resourcePath = "/path1/resource2"; - resource = host.concat(resourcePath); - this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_PROTECTED_RESOURCE_METADATA_ENDPOINT_URI).concat(resourcePath))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.RESOURCE).value(resource)) - .andReturn(); - - resourcePath = "/path1/path2/resource3"; - resource = host.concat(resourcePath); - this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_PROTECTED_RESOURCE_METADATA_ENDPOINT_URI).concat(resourcePath))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.RESOURCE).value(resource)) - .andReturn(); - } - - @Test - public void requestWhenProtectedResourceMetadataRequestAndMetadataCustomizerSetThenReturnCustomMetadataResponse() - throws Exception { - this.spring.register(ResourceServerConfigurationWithMetadataCustomizer.class).autowire(); - - this.mvc.perform(get(RESOURCE.concat(DEFAULT_OAUTH2_PROTECTED_RESOURCE_METADATA_ENDPOINT_URI))) - .andExpect(status().is2xxSuccessful()) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.RESOURCE).value(RESOURCE)) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.AUTHORIZATION_SERVERS).isArray()) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.AUTHORIZATION_SERVERS).value(hasSize(2))) - .andExpect( - jsonPath(OAuth2ProtectedResourceMetadataClaimNames.AUTHORIZATION_SERVERS).value(hasItem(ISSUER_1))) - .andExpect( - jsonPath(OAuth2ProtectedResourceMetadataClaimNames.AUTHORIZATION_SERVERS).value(hasItem(ISSUER_2))) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.SCOPES_SUPPORTED).isArray()) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.SCOPES_SUPPORTED).value(hasSize(2))) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.SCOPES_SUPPORTED).value(hasItem("scope1"))) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.SCOPES_SUPPORTED).value(hasItem("scope2"))) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.BEARER_METHODS_SUPPORTED).isArray()) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.BEARER_METHODS_SUPPORTED).value(hasSize(1))) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.BEARER_METHODS_SUPPORTED) - .value(hasItem("header"))) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.RESOURCE_NAME).value("resourceName")) - .andExpect(jsonPath(OAuth2ProtectedResourceMetadataClaimNames.TLS_CLIENT_CERTIFICATE_BOUND_ACCESS_TOKENS) - .value(true)) - .andReturn(); - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class ResourceServerConfiguration { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> - authorize - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2) -> - oauth2 - .jwt(Customizer.withDefaults()) - ); - // @formatter:on - return http.build(); - } - - @Bean - JwtDecoder jwtDecoder() { - return NimbusJwtDecoder.withPublicKey(TestKeys.DEFAULT_PUBLIC_KEY).build(); - } - - } - - @EnableWebSecurity - @Configuration(proxyBeanMethods = false) - static class ResourceServerConfigurationWithMetadataCustomizer extends ResourceServerConfiguration { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> - authorize - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2) -> - oauth2 - .jwt(Customizer.withDefaults()) - .protectedResourceMetadata((metadata) -> - metadata.protectedResourceMetadataCustomizer(protectedResourceMetadataCustomizer()) - ) - ); - // @formatter:on - return http.build(); - } - - private Consumer protectedResourceMetadataCustomizer() { - return (protectedResourceMetadata) -> protectedResourceMetadata.authorizationServer(ISSUER_1) - .authorizationServer(ISSUER_2) - .scope("scope1") - .scope("scope2") - .resourceName("resourceName"); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java deleted file mode 100644 index 079715e5fa1..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java +++ /dev/null @@ -1,2796 +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.annotation.web.configurers.oauth2.server.resource; - -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; -import java.security.KeyFactory; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.X509EncodedKeySpec; -import java.time.Clock; -import java.time.Duration; -import java.time.Instant; -import java.time.ZoneId; -import java.util.Base64; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.stream.Collectors; - -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jose.JWSObject; -import com.nimbusds.jose.Payload; -import com.nimbusds.jose.crypto.RSASSASigner; -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jose.util.JSONObjectUtils; -import jakarta.annotation.PreDestroy; -import jakarta.servlet.http.HttpServletRequest; -import net.minidev.json.JSONObject; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.hamcrest.core.AllOf; -import org.hamcrest.core.StringContains; -import org.hamcrest.core.StringEndsWith; -import org.hamcrest.core.StringStartsWith; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.verification.VerificationMode; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.NoUniqueBeanDefinitionException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.context.ApplicationContext; -import org.springframework.context.EnvironmentAware; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.convert.converter.Converter; -import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.Environment; -import org.springframework.core.env.PropertySource; -import org.springframework.core.io.ClassPathResource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.AuthenticationEventPublisher; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationManagerResolver; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextChangedListener; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.core.DefaultOAuth2AuthenticatedPrincipal; -import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2TokenValidator; -import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; -import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; -import org.springframework.security.oauth2.jose.TestKeys; -import org.springframework.security.oauth2.jwt.BadJwtException; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimNames; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtTimestampValidator; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; -import org.springframework.security.oauth2.jwt.TestJwts; -import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver; -import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter; -import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; -import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector; -import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; -import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; -import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; -import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler; -import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationConverter; -import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.security.web.access.AccessDeniedHandlerImpl; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.ResultMatcher; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.test.web.servlet.request.RequestPostProcessor; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.client.RestOperations; -import org.springframework.web.context.support.GenericWebApplicationContext; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link OAuth2ResourceServerConfigurer} - * - * @author Josh Cummings - * @author Evgeniy Cheban - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2ResourceServerConfigurerTests { - - private static final String JWT_TOKEN = "token"; - - private static final String JWT_SUBJECT = "mock-test-subject"; - - private static final Map JWT_CLAIMS = Collections.singletonMap(JwtClaimNames.SUB, JWT_SUBJECT); - - private static final Jwt JWT = TestJwts.jwt().build(); - - private static final String JWK_SET_URI = "https://mock.org"; - - private static final JwtAuthenticationToken JWT_AUTHENTICATION_TOKEN = new JwtAuthenticationToken(JWT, - Collections.emptyList()); - - private static final String INTROSPECTION_URI = "https://idp.example.com"; - - private static final String CLIENT_ID = "client-id"; - - private static final String CLIENT_SECRET = "client-secret"; - - private static final BearerTokenAuthentication INTROSPECTION_AUTHENTICATION_TOKEN = new BearerTokenAuthentication( - new DefaultOAuth2AuthenticatedPrincipal(JWT_CLAIMS, Collections.emptyList()), - TestOAuth2AccessTokens.noScopes(), Collections.emptyList()); - - @Autowired(required = false) - MockMvc mvc; - - @Autowired(required = false) - MockWebServer web; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - public void getWhenUsingDefaultsWithValidBearerTokenThenAcceptsRequest() throws Exception { - this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isOk()) - .andExpect(content().string("ok")); - // @formatter:on - } - - @Test - public void getWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { - this.spring - .register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class, - SecurityContextChangedListenerConfig.class) - .autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isOk()) - .andExpect(content().string("ok")); - // @formatter:on - verifyBean(SecurityContextHolderStrategy.class, atLeastOnce()).getContext(); - } - - @Test - public void getWhenSecurityContextHolderStrategyThenUses() throws Exception { - this.spring - .register(RestOperationsConfig.class, DefaultConfig.class, SecurityContextChangedListenerConfig.class, - BasicController.class) - .autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isOk()) - .andExpect(content().string("ok")); - // @formatter:on - verifyBean(SecurityContextChangedListener.class, atLeastOnce()).securityContextChanged(any()); - } - - @Test - public void getWhenUsingDefaultsInLambdaWithValidBearerTokenThenAcceptsRequest() throws Exception { - this.spring.register(RestOperationsConfig.class, DefaultInLambdaConfig.class, BasicController.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isOk()) - .andExpect(content().string("ok")); - // @formatter:on - } - - @Test - public void getWhenUsingJwkSetUriThenAcceptsRequest() throws Exception { - this.spring.register(WebServerConfig.class, JwkSetUriConfig.class, BasicController.class).autowire(); - mockWebServer(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isOk()) - .andExpect(content().string("ok")); - // @formatter:on - } - - @Test - public void getWhenUsingJwkSetUriInLambdaThenAcceptsRequest() throws Exception { - this.spring.register(WebServerConfig.class, JwkSetUriInLambdaConfig.class, BasicController.class).autowire(); - mockWebServer(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isOk()) - .andExpect(content().string("ok")); - // @formatter:on - } - - @Test - public void getWhenUsingDefaultsWithExpiredBearerTokenThenInvalidToken() throws Exception { - this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("Expired"); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); - // @formatter:on - } - - @Test - public void getWhenUsingDefaultsWithBadJwkEndpointThen500() throws Exception { - this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire(); - mockRestOperations("malformed"); - String token = this.token("ValidNoScopes"); - // @formatter:off - assertThatExceptionOfType(AuthenticationServiceException.class) - .isThrownBy(() -> this.mvc.perform(get("/").with(bearerToken(token)))); - // @formatter:on - } - - @Test - public void getWhenUsingDefaultsWithUnavailableJwkEndpointThen500() throws Exception { - this.spring.register(WebServerConfig.class, JwkSetUriConfig.class).autowire(); - this.web.shutdown(); - String token = this.token("ValidNoScopes"); - // @formatter:off - assertThatExceptionOfType(AuthenticationServiceException.class) - .isThrownBy(() -> this.mvc.perform(get("/").with(bearerToken(token)))); - // @formatter:on - } - - @Test - public void getWhenUsingDefaultsWithMalformedBearerTokenThenInvalidToken() throws Exception { - this.spring.register(JwkSetUriConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken("an\"invalid\"token"))) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("Bearer token is malformed")); - // @formatter:on - } - - @Test - public void getWhenUsingDefaultsWithMalformedPayloadThenInvalidToken() throws Exception { - this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("MalformedPayload"); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt: Malformed payload")); - // @formatter:on - } - - @Test - public void getWhenUsingDefaultsWithUnsignedBearerTokenThenInvalidToken() throws Exception { - this.spring.register(JwkSetUriConfig.class).autowire(); - String token = this.token("Unsigned"); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("Unsupported algorithm of none")); - // @formatter:on - } - - @Test - public void getWhenUsingDefaultsWithBearerTokenBeforeNotBeforeThenInvalidToken() throws Exception { - this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire(); - this.mockJwksRestOperations(jwks("Default")); - String token = this.token("TooEarly"); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); - // @formatter:on - } - - @Test - public void getWhenUsingDefaultsWithBearerTokenInTwoPlacesThenInvalidRequest() throws Exception { - this.spring.register(JwkSetUriConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken("token")).with(bearerToken("token").asParam())) - .andExpect(status().isBadRequest()) - .andExpect(invalidRequestHeader("Found multiple bearer tokens in the request")); - // @formatter:on - } - - @Test - public void getWhenUsingDefaultsWithBearerTokenInTwoParametersThenInvalidRequest() throws Exception { - this.spring.register(JwkSetUriConfig.class).autowire(); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("access_token", "token1"); - params.add("access_token", "token2"); - // @formatter:off - this.mvc.perform(get("/").params(params)) - .andExpect(status().isBadRequest()) - .andExpect(invalidRequestHeader("Found multiple bearer tokens in the request")); - // @formatter:on - } - - @Test - public void postWhenUsingDefaultsWithBearerTokenAsFormParameterThenIgnoresToken() throws Exception { - this.spring.register(JwkSetUriConfig.class).autowire(); - // engage csrf - // @formatter:off - this.mvc.perform(post("/").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken("token").asParam())) - .andExpect(status().isForbidden()) - .andExpect(header().doesNotExist(HttpHeaders.WWW_AUTHENTICATE)); - // @formatter:on - } - - @Test - public void postWhenCsrfDisabledWithBearerTokenAsFormParameterThenIgnoresToken() throws Exception { - this.spring.register(CsrfDisabledConfig.class).autowire(); - // @formatter:off - this.mvc.perform(post("/").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken("token").asParam())) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\"")); - // @formatter:on - } - - // gh-8031 - @Test - public void getWhenAnonymousDisabledThenAllows() throws Exception { - this.spring.register(RestOperationsConfig.class, AnonymousDisabledConfig.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken(token))) - .andExpect(status().isNotFound()); - // @formatter:on - } - - @Test - public void getWhenUsingDefaultsWithNoBearerTokenThenUnauthorized() throws Exception { - this.spring.register(JwkSetUriConfig.class).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\"")); - // @formatter:on - } - - @Test - public void getWhenUsingDefaultsWithSufficientlyScopedBearerTokenThenAcceptsRequest() throws Exception { - this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidMessageReadScope"); - // @formatter:off - this.mvc.perform(get("/requires-read-scope").with(bearerToken(token))) - .andExpect(status().isOk()) - .andExpect(content().string("[SCOPE_message:read]")); - // @formatter:on - } - - @Test - public void getWhenUsingDefaultsWithInsufficientScopeThenInsufficientScopeError() throws Exception { - this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/requires-read-scope").with(bearerToken(token))) - .andExpect(status().isForbidden()) - .andExpect(insufficientScopeHeader()); - // @formatter:on - } - - @Test - public void getWhenUsingDefaultsWithInsufficientScpThenInsufficientScopeError() throws Exception { - this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidMessageWriteScp"); - // @formatter:off - this.mvc.perform(get("/requires-read-scope").with(bearerToken(token))) - .andExpect(status().isForbidden()) - .andExpect(insufficientScopeHeader()); - // @formatter:on - } - - @Test - public void getWhenUsingDefaultsAndAuthorizationServerHasNoMatchingKeyThenInvalidToken() throws Exception { - this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire(); - mockJwksRestOperations(jwks("Empty")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); - // @formatter:on - } - - @Test - public void getWhenUsingDefaultsAndAuthorizationServerHasMultipleMatchingKeysThenOk() throws Exception { - this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); - mockJwksRestOperations(jwks("TwoKeys")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken(token))) - .andExpect(status().isOk()) - .andExpect(content().string("test-subject")); - // @formatter:on - } - - @Test - public void getWhenUsingDefaultsAndKeyMatchesByKidThenOk() throws Exception { - this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); - mockJwksRestOperations(jwks("TwoKeys")); - String token = this.token("Kid"); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken(token))) - .andExpect(status().isOk()) - .andExpect(content().string("test-subject")); - // @formatter:on - } - - @Test - public void getWhenUsingMethodSecurityWithValidBearerTokenThenAcceptsRequest() throws Exception { - this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidMessageReadScope"); - // @formatter:off - this.mvc.perform(get("/ms-requires-read-scope").with(bearerToken(token))) - .andExpect(status().isOk()) - .andExpect(content().string("[SCOPE_message:read]")); - // @formatter:on - } - - @Test - public void getWhenUsingMethodSecurityWithValidBearerTokenHavingScpAttributeThenAcceptsRequest() throws Exception { - this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidMessageReadScp"); - // @formatter:off - this.mvc.perform(get("/ms-requires-read-scope").with(bearerToken(token))) - .andExpect(status().isOk()) - .andExpect(content().string("[SCOPE_message:read]")); - // @formatter:on - } - - @Test - public void getWhenUsingMethodSecurityWithInsufficientScopeThenInsufficientScopeError() throws Exception { - this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/ms-requires-read-scope").with(bearerToken(token))) - .andExpect(status().isForbidden()) - .andExpect(insufficientScopeHeader()); - // @formatter:on - } - - @Test - public void getWhenUsingMethodSecurityWithInsufficientScpThenInsufficientScopeError() throws Exception { - this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidMessageWriteScp"); - // @formatter:off - this.mvc.perform(get("/ms-requires-read-scope").with(bearerToken(token))) - .andExpect(status().isForbidden()) - .andExpect(insufficientScopeHeader()); - // @formatter:on - } - - @Test - public void getWhenUsingMethodSecurityWithDenyAllThenInsufficientScopeError() throws Exception { - this.spring.register(RestOperationsConfig.class, MethodSecurityConfig.class, BasicController.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidMessageReadScope"); - // @formatter:off - this.mvc.perform(get("/ms-deny").with(bearerToken(token))) - .andExpect(status().isForbidden()) - .andExpect(insufficientScopeHeader()); - // @formatter:on - } - - @Test - public void postWhenUsingDefaultsWithValidBearerTokenAndNoCsrfTokenThenOk() throws Exception { - this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken(token))) - .andExpect(status().isOk()) - .andExpect(content().string("test-subject")); - // @formatter:on - } - - @Test - public void postWhenUsingDefaultsWithNoBearerTokenThenCsrfDenies() throws Exception { - this.spring.register(JwkSetUriConfig.class).autowire(); - // @formatter:off - this.mvc.perform(post("/authenticated")) - .andExpect(status().isForbidden()) - .andExpect(header().doesNotExist(HttpHeaders.WWW_AUTHENTICATE)); - // @formatter:on - } - - @Test - public void postWhenUsingDefaultsWithExpiredBearerTokenAndNoCsrfThenInvalidToken() throws Exception { - this.spring.register(RestOperationsConfig.class, DefaultConfig.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("Expired"); - // @formatter:off - this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).with(bearerToken(token))) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); - // @formatter:on - } - - @Test - public void requestWhenDefaultConfiguredThenSessionIsNotCreated() throws Exception { - this.spring.register(RestOperationsConfig.class, DefaultConfig.class, BasicController.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - MvcResult result = this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isOk()) - .andReturn(); - // @formatter:on - assertThat(result.getRequest().getSession(false)).isNull(); - } - - @Test - public void requestWhenIntrospectionConfiguredThenSessionIsNotCreated() throws Exception { - this.spring.register(RestOperationsConfig.class, OpaqueTokenConfig.class, BasicController.class).autowire(); - mockJsonRestOperations(json("Active")); - // @formatter:off - MvcResult result = this.mvc.perform(get("/authenticated").with(bearerToken("token"))) - .andExpect(status().isOk()) - .andExpect(content().string("test-subject")) - .andReturn(); - // @formatter:on - assertThat(result.getRequest().getSession(false)).isNull(); - } - - @Test - public void requestWhenUsingDefaultsAndNoBearerTokenThenSessionIsCreated() throws Exception { - this.spring.register(JwkSetUriConfig.class, BasicController.class).autowire(); - // @formatter:off - MvcResult result = this.mvc.perform(get("/")) - .andExpect(status().isUnauthorized()) - .andReturn(); - // @formatter:on - assertThat(result.getRequest().getSession(false)).isNotNull(); - } - - @Test - public void requestWhenSessionManagementConfiguredThenUserConfigurationOverrides() throws Exception { - this.spring.register(RestOperationsConfig.class, AlwaysSessionCreationConfig.class, BasicController.class) - .autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - MvcResult result = this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isOk()) - .andReturn(); - // @formatter:on - assertThat(result.getRequest().getSession(false)).isNotNull(); - } - - @Test - public void requestWhenBearerTokenResolverAllowsRequestBodyThenEitherHeaderOrRequestBodyIsAccepted() - throws Exception { - this.spring.register(AllowBearerTokenInRequestBodyConfig.class, JwtDecoderConfig.class, BasicController.class) - .autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willReturn(JWT); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN))) - .andExpect(status().isOk()) - .andExpect(content().string(JWT_SUBJECT)); - this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).param("access_token", JWT_TOKEN)) - .andExpect(status().isOk()) - .andExpect(content().string(JWT_SUBJECT)); - // @formatter:on - } - - @Test - public void requestWhenBearerTokenResolverAllowsQueryParameterThenEitherHeaderOrQueryParameterIsAccepted() - throws Exception { - this.spring - .register(AllowBearerTokenAsQueryParameterConfig.class, JwtDecoderConfig.class, BasicController.class) - .autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willReturn(JWT); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN))) - .andExpect(status().isOk()) - .andExpect(content().string(JWT_SUBJECT)); - this.mvc.perform(get("/authenticated").param("access_token", JWT_TOKEN)) - .andExpect(status().isOk()) - .andExpect(content().string(JWT_SUBJECT)); - // @formatter:on - } - - @Test - public void requestWhenBearerTokenResolverAllowsRequestBodyAndRequestContainsTwoTokensThenInvalidRequest() - throws Exception { - this.spring.register(AllowBearerTokenInRequestBodyConfig.class, JwtDecoderConfig.class, BasicController.class) - .autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willReturn(JWT); - // @formatter:off - MockHttpServletRequestBuilder request = post("/authenticated") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) - .param("access_token", JWT_TOKEN) - .with(bearerToken(JWT_TOKEN)) - .with(csrf()); - this.mvc.perform(request) - .andExpect(status().isBadRequest()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("invalid_request"))); - // @formatter:on - } - - @Test - public void requestWhenBearerTokenResolverAllowsQueryParameterAndRequestContainsTwoTokensThenInvalidRequest() - throws Exception { - this.spring - .register(AllowBearerTokenAsQueryParameterConfig.class, JwtDecoderConfig.class, BasicController.class) - .autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willReturn(JWT); - // @formatter:off - MockHttpServletRequestBuilder request = get("/authenticated") - .with(bearerToken(JWT_TOKEN)) - .param("access_token", JWT_TOKEN); - this.mvc.perform(request) - .andExpect(status().isBadRequest()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("invalid_request"))); - // @formatter:on - } - - @Test - public void getBearerTokenResolverWhenDuplicateResolverBeansAndAnotherOnTheDslThenTheDslOneIsUsed() { - BearerTokenResolver resolverBean = mock(BearerTokenResolver.class); - BearerTokenResolver resolver = mock(BearerTokenResolver.class); - GenericWebApplicationContext context = new GenericWebApplicationContext(); - context.registerBean("resolverOne", BearerTokenResolver.class, () -> resolverBean); - context.registerBean("resolverTwo", BearerTokenResolver.class, () -> resolverBean); - this.spring.context(context).autowire(); - OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context); - oauth2.bearerTokenResolver(resolver); - assertThat(oauth2.getBearerTokenResolver()).isEqualTo(resolver); - } - - @Test - public void getBearerTokenResolverWhenDuplicateResolverBeansThenWiringException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(MultipleBearerTokenResolverBeansConfig.class, JwtDecoderConfig.class) - .autowire()) - .withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class); - } - - @Test - public void getBearerTokenResolverWhenResolverBeanAndAnotherOnTheDslThenTheDslOneIsUsed() { - BearerTokenResolver resolver = mock(BearerTokenResolver.class); - BearerTokenResolver resolverBean = mock(BearerTokenResolver.class); - GenericWebApplicationContext context = new GenericWebApplicationContext(); - context.registerBean(BearerTokenResolver.class, () -> resolverBean); - this.spring.context(context).autowire(); - OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context); - oauth2.bearerTokenResolver(resolver); - assertThat(oauth2.getBearerTokenResolver()).isEqualTo(resolver); - } - - @Test - public void requestWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception { - this.spring.register(CustomAuthenticationDetailsSource.class, JwtDecoderConfig.class, BasicController.class) - .autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willReturn(JWT); - this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN))) - .andExpect(status().isOk()) - .andExpect(content().string(JWT_SUBJECT)); - verifyBean(AuthenticationDetailsSource.class).buildDetails(any()); - } - - @Test - public void requestWhenCustomJwtDecoderWiredOnDslThenUsed() throws Exception { - this.spring.register(CustomJwtDecoderOnDsl.class, BasicController.class).autowire(); - CustomJwtDecoderOnDsl config = this.spring.getContext().getBean(CustomJwtDecoderOnDsl.class); - JwtDecoder decoder = config.decoder(); - given(decoder.decode(anyString())).willReturn(JWT); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN))) - .andExpect(status().isOk()) - .andExpect(content().string(JWT_SUBJECT)); - // @formatter:on - } - - @Test - public void requestWhenCustomJwtDecoderInLambdaOnDslThenUsed() throws Exception { - this.spring.register(CustomJwtDecoderInLambdaOnDsl.class, BasicController.class).autowire(); - CustomJwtDecoderInLambdaOnDsl config = this.spring.getContext().getBean(CustomJwtDecoderInLambdaOnDsl.class); - JwtDecoder decoder = config.decoder(); - given(decoder.decode(anyString())).willReturn(JWT); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN))) - .andExpect(status().isOk()) - .andExpect(content().string(JWT_SUBJECT)); - // @formatter:on - } - - @Test - public void requestWhenCustomJwtDecoderExposedAsBeanThenUsed() throws Exception { - this.spring.register(CustomJwtDecoderAsBean.class, BasicController.class).autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willReturn(JWT); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken(JWT_TOKEN))) - .andExpect(status().isOk()) - .andExpect(content().string(JWT_SUBJECT)); - // @formatter:on - } - - @Test - public void getJwtDecoderWhenConfiguredWithDecoderAndJwkSetUriThenLastOneWins() { - ApplicationContext context = mock(ApplicationContext.class); - JwtDecoder decoder = mock(JwtDecoder.class); - new OAuth2ResourceServerConfigurer(context).jwt((jwt) -> { - jwt.jwkSetUri(JWK_SET_URI); - jwt.decoder(decoder); - assertThat(jwt.getJwtDecoder()).isEqualTo(decoder); - }); - new OAuth2ResourceServerConfigurer(context).jwt((jwt) -> { - jwt.decoder(decoder).jwkSetUri(JWK_SET_URI); - assertThat(jwt.getJwtDecoder()).isInstanceOf(NimbusJwtDecoder.class); - }); - } - - @Test - public void getJwtDecoderWhenConflictingJwtDecodersThenTheDslWiredOneTakesPrecedence() { - JwtDecoder decoderBean = mock(JwtDecoder.class); - JwtDecoder decoder = mock(JwtDecoder.class); - ApplicationContext context = mock(ApplicationContext.class); - given(context.getBean(JwtDecoder.class)).willReturn(decoderBean); - new OAuth2ResourceServerConfigurer(context).jwt((jwt) -> { - jwt.decoder(decoder); - assertThat(jwt.getJwtDecoder()).isEqualTo(decoder); - }); - } - - @Test - public void getJwtDecoderWhenContextHasBeanAndUserConfiguresJwkSetUriThenJwkSetUriTakesPrecedence() { - JwtDecoder decoder = mock(JwtDecoder.class); - ApplicationContext context = mock(ApplicationContext.class); - given(context.getBean(JwtDecoder.class)).willReturn(decoder); - new OAuth2ResourceServerConfigurer(context).jwt((jwt) -> { - jwt.jwkSetUri(JWK_SET_URI); - assertThat(jwt.getJwtDecoder()).isNotEqualTo(decoder); - assertThat(jwt.getJwtDecoder()).isInstanceOf(NimbusJwtDecoder.class); - }); - } - - @Test - public void getJwtDecoderWhenTwoJwtDecoderBeansAndAnotherWiredOnDslThenDslWiredOneTakesPrecedence() { - JwtDecoder decoderBean = mock(JwtDecoder.class); - JwtDecoder decoder = mock(JwtDecoder.class); - GenericWebApplicationContext context = new GenericWebApplicationContext(); - context.registerBean("decoderOne", JwtDecoder.class, () -> decoderBean); - context.registerBean("decoderTwo", JwtDecoder.class, () -> decoderBean); - this.spring.context(context).autowire(); - new OAuth2ResourceServerConfigurer(context).jwt((jwt) -> { - jwt.decoder(decoder); - assertThat(jwt.getJwtDecoder()).isEqualTo(decoder); - }); - } - - @Test - public void getJwtDecoderWhenTwoJwtDecoderBeansThenThrowsException() { - JwtDecoder decoder = mock(JwtDecoder.class); - GenericWebApplicationContext context = new GenericWebApplicationContext(); - context.registerBean("decoderOne", JwtDecoder.class, () -> decoder); - context.registerBean("decoderTwo", JwtDecoder.class, () -> decoder); - this.spring.context(context).autowire(); - new OAuth2ResourceServerConfigurer(context) - .jwt((jwt) -> assertThatExceptionOfType(NoUniqueBeanDefinitionException.class) - .isThrownBy(jwt::getJwtDecoder)); - } - - @Test - public void requestWhenRealmNameConfiguredThenUsesOnUnauthenticated() throws Exception { - this.spring.register(RealmNameConfiguredOnEntryPoint.class, JwtDecoderConfig.class).autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willThrow(BadJwtException.class); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken("invalid_token"))) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer realm=\"myRealm\""))); - // @formatter:on - } - - @Test - public void requestWhenRealmNameConfiguredThenUsesOnAccessDenied() throws Exception { - this.spring.register(RealmNameConfiguredOnAccessDeniedHandler.class, JwtDecoderConfig.class).autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willReturn(JWT); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken("insufficiently_scoped"))) - .andExpect(status().isForbidden()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer realm=\"myRealm\""))); - // @formatter:on - } - - @Test - public void authenticationEntryPointWhenGivenNullThenThrowsException() { - ApplicationContext context = mock(ApplicationContext.class); - OAuth2ResourceServerConfigurer configurer = new OAuth2ResourceServerConfigurer(context); - assertThatIllegalArgumentException().isThrownBy(() -> configurer.authenticationEntryPoint(null)); - } - - @Test - public void accessDeniedHandlerWhenGivenNullThenThrowsException() { - ApplicationContext context = mock(ApplicationContext.class); - OAuth2ResourceServerConfigurer configurer = new OAuth2ResourceServerConfigurer(context); - assertThatIllegalArgumentException().isThrownBy(() -> configurer.accessDeniedHandler(null)); - } - - @Test - public void requestWhenCustomJwtValidatorFailsThenCorrespondingErrorMessage() throws Exception { - this.spring.register(RestOperationsConfig.class, CustomJwtValidatorConfig.class).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - OAuth2TokenValidator jwtValidator = this.spring.getContext() - .getBean(CustomJwtValidatorConfig.class) - .getJwtValidator(); - OAuth2Error error = new OAuth2Error("custom-error", "custom-description", "custom-uri"); - given(jwtValidator.validate(any(Jwt.class))).willReturn(OAuth2TokenValidatorResult.failure(error)); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("custom-description"))); - // @formatter:on - } - - @Test - public void requestWhenClockSkewSetThenTimestampWindowRelaxedAccordingly() throws Exception { - this.spring.register(RestOperationsConfig.class, UnexpiredJwtClockSkewConfig.class, BasicController.class) - .autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ExpiresAt4687177990"); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void requestWhenClockSkewSetButJwtStillTooLateThenReportsExpired() throws Exception { - this.spring.register(RestOperationsConfig.class, ExpiredJwtClockSkewConfig.class, BasicController.class) - .autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ExpiresAt4687177990"); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("Jwt expired at")); - // @formatter:on - } - - @Test - public void requestWhenJwtAuthenticationConverterConfiguredOnDslThenIsUsed() throws Exception { - this.spring - .register(JwtDecoderConfig.class, JwtAuthenticationConverterConfiguredOnDsl.class, BasicController.class) - .autowire(); - Converter jwtAuthenticationConverter = this.spring.getContext() - .getBean(JwtAuthenticationConverterConfiguredOnDsl.class) - .getJwtAuthenticationConverter(); - given(jwtAuthenticationConverter.convert(JWT)).willReturn(JWT_AUTHENTICATION_TOKEN); - JwtDecoder jwtDecoder = this.spring.getContext().getBean(JwtDecoder.class); - given(jwtDecoder.decode(anyString())).willReturn(JWT); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(JWT_TOKEN))) - .andExpect(status().isOk()); - // @formatter:on - verify(jwtAuthenticationConverter).convert(JWT); - } - - @Test - public void requestWhenJwtAuthenticationConverterCustomizedAuthoritiesThenThoseAuthoritiesArePropagated() - throws Exception { - this.spring.register(JwtDecoderConfig.class, CustomAuthorityMappingConfig.class, BasicController.class) - .autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(JWT_TOKEN)).willReturn(JWT); - // @formatter:off - this.mvc.perform(get("/requires-read-scope").with(bearerToken(JWT_TOKEN))) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void requestWhenUsingPublicKeyAndValidTokenThenAuthenticates() throws Exception { - this.spring.register(SingleKeyConfig.class, BasicController.class).autowire(); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void requestWhenUsingPublicKeyAndSignatureFailsThenReturnsInvalidToken() throws Exception { - this.spring.register(SingleKeyConfig.class).autowire(); - String token = this.token("WrongSignature"); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(invalidTokenHeader("signature")); - // @formatter:on - } - - @Test - public void requestWhenUsingPublicKeyAlgorithmDoesNotMatchThenReturnsInvalidToken() throws Exception { - this.spring.register(SingleKeyConfig.class).autowire(); - String token = this.token("WrongAlgorithm"); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken(token))) - .andExpect(invalidTokenHeader("algorithm")); - // @formatter:on - } - - // gh-7793 - @Test - public void requestWhenUsingCustomAuthenticationEventPublisherThenUses() throws Exception { - this.spring.register(CustomAuthenticationEventPublisher.class).autowire(); - given(bean(JwtDecoder.class).decode(anyString())).willThrow(new BadJwtException("problem")); - this.mvc.perform(get("/").with(bearerToken("token"))); - verifyBean(AuthenticationEventPublisher.class) - .publishAuthenticationFailure(any(OAuth2AuthenticationException.class), any(Authentication.class)); - } - - @Test - public void getWhenCustomJwtAuthenticationManagerThenUsed() throws Exception { - this.spring.register(JwtAuthenticationManagerConfig.class, BasicController.class).autowire(); - given(bean(AuthenticationProvider.class).authenticate(any(Authentication.class))) - .willReturn(JWT_AUTHENTICATION_TOKEN); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken("token"))) - .andExpect(status().isOk()) - .andExpect(content().string("mock-test-subject")); - // @formatter:on - verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class)); - } - - @Test - public void getWhenDefaultAndCustomJwtAuthenticationManagerThenCustomUsed() throws Exception { - this.spring.register(DefaultAndJwtAuthenticationManagerConfig.class, BasicController.class).autowire(); - DefaultAndJwtAuthenticationManagerConfig config = this.spring.getContext() - .getBean(DefaultAndJwtAuthenticationManagerConfig.class); - AuthenticationManager defaultAuthenticationManager = config.defaultAuthenticationManager(); - AuthenticationManager jwtAuthenticationManager = config.jwtAuthenticationManager(); - given(defaultAuthenticationManager.authenticate(any())) - .willThrow(new RuntimeException("should not interact with default auth manager")); - given(jwtAuthenticationManager.authenticate(any())).willReturn(JWT_AUTHENTICATION_TOKEN); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken("token"))) - .andExpect(status().isOk()) - .andExpect(content().string("mock-test-subject")); - // @formatter:on - verify(jwtAuthenticationManager).authenticate(any(Authentication.class)); - } - - @Test - public void getWhenIntrospectingThenOk() throws Exception { - this.spring.register(RestOperationsConfig.class, OpaqueTokenConfig.class, BasicController.class).autowire(); - mockJsonRestOperations(json("Active")); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken("token"))) - .andExpect(status().isOk()) - .andExpect(content().string("test-subject")); - // @formatter:on - } - - @Test - public void getWhenOpaqueTokenInLambdaAndIntrospectingThenOk() throws Exception { - this.spring.register(RestOperationsConfig.class, OpaqueTokenInLambdaConfig.class, BasicController.class) - .autowire(); - mockJsonRestOperations(json("Active")); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken("token"))) - .andExpect(status().isOk()) - .andExpect(content().string("test-subject")); - // @formatter:on - } - - @Test - public void getWhenIntrospectionFailsThenUnauthorized() throws Exception { - this.spring.register(RestOperationsConfig.class, OpaqueTokenConfig.class).autowire(); - mockJsonRestOperations(json("Inactive")); - // @formatter:off - this.mvc.perform(get("/").with(bearerToken("token"))) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("Provided token isn't active"))); - // @formatter:on - } - - @Test - public void getWhenIntrospectionLacksScopeThenForbidden() throws Exception { - this.spring.register(RestOperationsConfig.class, OpaqueTokenConfig.class).autowire(); - mockJsonRestOperations(json("ActiveNoScopes")); - // @formatter:off - this.mvc.perform(get("/requires-read-scope").with(bearerToken("token"))) - .andExpect(status().isForbidden()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("scope"))); - // @formatter:on - } - - @Test - public void getWhenCustomIntrospectionAuthenticationManagerThenUsed() throws Exception { - this.spring.register(OpaqueTokenAuthenticationManagerConfig.class, BasicController.class).autowire(); - given(bean(AuthenticationProvider.class).authenticate(any(Authentication.class))) - .willReturn(INTROSPECTION_AUTHENTICATION_TOKEN); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken("token"))) - .andExpect(status().isOk()) - .andExpect(content().string("mock-test-subject")); - // @formatter:on - verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class)); - } - - @Test - public void getWhenDefaultAndCustomIntrospectionAuthenticationManagerThenCustomUsed() throws Exception { - this.spring.register(DefaultAndOpaqueTokenAuthenticationManagerConfig.class, BasicController.class).autowire(); - DefaultAndOpaqueTokenAuthenticationManagerConfig config = this.spring.getContext() - .getBean(DefaultAndOpaqueTokenAuthenticationManagerConfig.class); - AuthenticationManager defaultAuthenticationManager = config.defaultAuthenticationManager(); - AuthenticationManager opaqueTokenAuthenticationManager = config.opaqueTokenAuthenticationManager(); - given(defaultAuthenticationManager.authenticate(any())) - .willThrow(new RuntimeException("should not interact with default auth manager")); - given(opaqueTokenAuthenticationManager.authenticate(any())).willReturn(INTROSPECTION_AUTHENTICATION_TOKEN); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken("token"))) - .andExpect(status().isOk()) - .andExpect(content().string("mock-test-subject")); - // @formatter:on - verify(opaqueTokenAuthenticationManager).authenticate(any(Authentication.class)); - } - - @Test - public void getWhenCustomIntrospectionAuthenticationManagerInLambdaThenUsed() throws Exception { - this.spring.register(OpaqueTokenAuthenticationManagerInLambdaConfig.class, BasicController.class).autowire(); - given(bean(AuthenticationProvider.class).authenticate(any(Authentication.class))) - .willReturn(INTROSPECTION_AUTHENTICATION_TOKEN); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken("token"))) - .andExpect(status().isOk()) - .andExpect(content().string("mock-test-subject")); - // @formatter:on - verifyBean(AuthenticationProvider.class).authenticate(any(Authentication.class)); - } - - @Test - public void configureWhenOnlyIntrospectionUrlThenException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(OpaqueTokenHalfConfiguredConfig.class).autowire()); - } - - @Test - public void getIntrospectionClientWhenConfiguredWithClientAndIntrospectionUriThenLastOneWins() { - ApplicationContext context = mock(ApplicationContext.class); - OpaqueTokenIntrospector client = mock(OpaqueTokenIntrospector.class); - new OAuth2ResourceServerConfigurer(context).opaqueToken((opaqueToken) -> { - opaqueToken.introspectionUri(INTROSPECTION_URI); - opaqueToken.introspectionClientCredentials(CLIENT_ID, CLIENT_SECRET); - opaqueToken.introspector(client); - assertThat(opaqueToken.getIntrospector()).isEqualTo(client); - }); - new OAuth2ResourceServerConfigurer(context).opaqueToken((opaqueToken) -> { - opaqueToken.introspector(client); - opaqueToken.introspectionUri(INTROSPECTION_URI); - opaqueToken.introspectionClientCredentials(CLIENT_ID, CLIENT_SECRET); - assertThat(opaqueToken.getIntrospector()).isNotSameAs(client); - }); - } - - @Test - public void getIntrospectionClientWhenDslAndBeanWiredThenDslTakesPrecedence() { - GenericApplicationContext context = new GenericApplicationContext(); - registerMockBean(context, "introspectionClientOne", OpaqueTokenIntrospector.class); - registerMockBean(context, "introspectionClientTwo", OpaqueTokenIntrospector.class); - new OAuth2ResourceServerConfigurer(context).opaqueToken((opaqueToken) -> { - opaqueToken.introspectionUri(INTROSPECTION_URI); - opaqueToken.introspectionClientCredentials(CLIENT_ID, CLIENT_SECRET); - assertThat(opaqueToken.getIntrospector()).isNotNull(); - }); - } - - @Test - public void requestWhenBasicAndResourceServerEntryPointsThenMatchedByRequest() throws Exception { - this.spring.register(BasicAndResourceServerConfig.class, JwtDecoderConfig.class).autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willThrow(BadJwtException.class); - // @formatter:off - this.mvc.perform(get("/authenticated").with(httpBasic("some", "user"))) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Basic"))); - this.mvc.perform(get("/authenticated")) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Basic"))); - this.mvc.perform(get("/authenticated").with(bearerToken("invalid_token"))) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer"))); - // @formatter:on - } - - @Test - public void requestWhenFormLoginAndResourceServerEntryPointsThenSessionCreatedByRequest() throws Exception { - this.spring.register(FormAndResourceServerConfig.class, JwtDecoderConfig.class).autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willThrow(BadJwtException.class); - // @formatter:off - MvcResult result = this.mvc.perform(get("/authenticated") - .header("Accept", "text/html")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")) - .andReturn(); - // @formatter:on - assertThat(result.getRequest().getSession(false)).isNotNull(); - // @formatter:off - result = this.mvc.perform(get("/authenticated").with(bearerToken("token"))) - .andExpect(status().isUnauthorized()) - .andReturn(); - // @formatter:on - assertThat(result.getRequest().getSession(false)).isNull(); - } - - @Test - public void unauthenticatedRequestWhenFormOAuth2LoginAndResourceServerThenNegotiates() throws Exception { - this.spring.register(OAuth2LoginAndResourceServerConfig.class, JwtDecoderConfig.class).autowire(); - this.mvc.perform(get("/any").header("X-Requested-With", "XMLHttpRequest")).andExpect(status().isUnauthorized()); - this.mvc.perform(get("/any").header("Accept", "application/json")).andExpect(status().isUnauthorized()); - this.mvc.perform(get("/any").header("Accept", "text/html")).andExpect(status().is3xxRedirection()); - this.mvc.perform(get("/any").header("Accept", "image/jpg")).andExpect(status().is3xxRedirection()); - } - - @Test - public void requestWhenDefaultAndResourceServerAccessDeniedHandlersThenMatchedByRequest() throws Exception { - this.spring - .register(ExceptionHandlingAndResourceServerWithAccessDeniedHandlerConfig.class, JwtDecoderConfig.class) - .autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willReturn(JWT); - // @formatter:off - this.mvc.perform(get("/authenticated").with(httpBasic("basic-user", "basic-password"))) - .andExpect(status().isForbidden()) - .andExpect(header().doesNotExist(HttpHeaders.WWW_AUTHENTICATE)); - this.mvc.perform(get("/authenticated").with(bearerToken("insufficiently_scoped"))) - .andExpect(status().isForbidden()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer"))); - // @formatter:on - } - - @Test - public void getWhenAlsoUsingHttpBasicThenCorrectProviderEngages() throws Exception { - this.spring.register(RestOperationsConfig.class, BasicAndResourceServerConfig.class, BasicController.class) - .autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken(token))) - .andExpect(status().isOk()) - .andExpect(content().string("test-subject")); - this.mvc.perform(get("/authenticated").with(httpBasic("basic-user", "basic-password"))) - .andExpect(status().isOk()) - .andExpect(content().string("basic-user")); - // @formatter:on - } - - @Test - public void getAuthenticationManagerWhenConfiguredAuthenticationManagerThenTakesPrecedence() { - ApplicationContext context = mock(ApplicationContext.class); - OAuth2ResourceServerConfigurer oauth2ResourceServer = new OAuth2ResourceServerConfigurer<>( - context); - AuthenticationManager authenticationManager = mock(AuthenticationManager.class); - oauth2ResourceServer - .jwt((jwt) -> jwt.authenticationManager(authenticationManager).decoder(mock(JwtDecoder.class))); - assertThat(oauth2ResourceServer.getAuthenticationManager(null)).isSameAs(authenticationManager); - oauth2ResourceServer = new OAuth2ResourceServerConfigurer<>(context); - oauth2ResourceServer.opaqueToken((opaqueToken) -> opaqueToken.authenticationManager(authenticationManager) - .introspector(mock(OpaqueTokenIntrospector.class))); - assertThat(oauth2ResourceServer.getAuthenticationManager(null)).isSameAs(authenticationManager); - } - - @Test - public void getWhenMultipleIssuersThenUsesIssuerClaimToDifferentiate() throws Exception { - this.spring.register(WebServerConfig.class, MultipleIssuersConfig.class, BasicController.class).autowire(); - MockWebServer server = this.spring.getContext().getBean(MockWebServer.class); - String metadata = "{\n" + " \"issuer\": \"%s\", \n" + " \"jwks_uri\": \"%s/.well-known/jwks.json\" \n" - + "}"; - String jwkSet = jwkSet(); - String issuerOne = server.url("/issuerOne").toString(); - String issuerTwo = server.url("/issuerTwo").toString(); - String issuerThree = server.url("/issuerThree").toString(); - String jwtOne = jwtFromIssuer(issuerOne); - String jwtTwo = jwtFromIssuer(issuerTwo); - String jwtThree = jwtFromIssuer(issuerThree); - mockWebServer(String.format(metadata, issuerOne, issuerOne)); - mockWebServer(jwkSet); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken(jwtOne))) - .andExpect(status().isOk()) - .andExpect(content().string("test-subject")); - // @formatter:on - mockWebServer(String.format(metadata, issuerTwo, issuerTwo)); - mockWebServer(jwkSet); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken(jwtTwo))) - .andExpect(status().isOk()) - .andExpect(content().string("test-subject")); - // @formatter:on - mockWebServer(String.format(metadata, issuerThree, issuerThree)); - mockWebServer(jwkSet); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken(jwtThree))) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("Invalid issuer")); - // @formatter:on - } - - @Test - public void configuredWhenMissingJwtAuthenticationProviderThenWiringException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(JwtlessConfig.class).autowire()) - .withMessageContaining("neither was found"); - } - - @Test - public void configureWhenMissingJwkSetUriThenWiringException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(JwtHalfConfiguredConfig.class).autowire()) - .withMessageContaining("No qualifying bean of type"); - } - - @Test - public void configureWhenUsingBothJwtAndOpaqueThenWiringException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(OpaqueAndJwtConfig.class).autowire()) - .withMessageContaining("Spring Security only supports JWTs or Opaque Tokens"); - } - - @Test - public void configureWhenUsingBothAuthenticationManagerResolverAndOpaqueThenWiringException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(AuthenticationManagerResolverPlusOtherConfig.class).autowire()) - .withMessageContaining("authenticationManagerResolver"); - } - - @Test - public void getJwtAuthenticationConverterWhenNoConverterSpecifiedThenTheDefaultIsUsed() { - ApplicationContext context = this.spring.context(new GenericWebApplicationContext()).getContext(); - new OAuth2ResourceServerConfigurer(context) - .jwt((jwt) -> assertThat(jwt.getJwtAuthenticationConverter()) - .isInstanceOf(JwtAuthenticationConverter.class)); - } - - @Test - public void getJwtAuthenticationConverterWhenConverterBeanSpecified() { - JwtAuthenticationConverter converterBean = new JwtAuthenticationConverter(); - GenericWebApplicationContext context = new GenericWebApplicationContext(); - context.registerBean(JwtAuthenticationConverter.class, () -> converterBean); - this.spring.context(context).autowire(); - new OAuth2ResourceServerConfigurer(context) - .jwt((jwt) -> assertThat(jwt.getJwtAuthenticationConverter()).isEqualTo(converterBean)); - } - - @Test - public void getJwtAuthenticationConverterWhenConverterBeanAndAnotherOnTheDslThenTheDslOneIsUsed() { - JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); - JwtAuthenticationConverter converterBean = new JwtAuthenticationConverter(); - GenericWebApplicationContext context = new GenericWebApplicationContext(); - context.registerBean(JwtAuthenticationConverter.class, () -> converterBean); - this.spring.context(context).autowire(); - new OAuth2ResourceServerConfigurer(context).jwt((jwt) -> { - jwt.jwtAuthenticationConverter(converter); - assertThat(jwt.getJwtAuthenticationConverter()).isEqualTo(converter); - }); - } - - @Test - public void getJwtAuthenticationConverterWhenDuplicateConverterBeansAndAnotherOnTheDslThenTheDslOneIsUsed() { - JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); - JwtAuthenticationConverter converterBean = new JwtAuthenticationConverter(); - GenericWebApplicationContext context = new GenericWebApplicationContext(); - context.registerBean("converterOne", JwtAuthenticationConverter.class, () -> converterBean); - context.registerBean("converterTwo", JwtAuthenticationConverter.class, () -> converterBean); - this.spring.context(context).autowire(); - new OAuth2ResourceServerConfigurer(context).jwt((jwt) -> { - jwt.jwtAuthenticationConverter(converter); - assertThat(jwt.getJwtAuthenticationConverter()).isEqualTo(converter); - }); - } - - @Test - public void getJwtAuthenticationConverterWhenDuplicateConverterBeansThenThrowsException() { - JwtAuthenticationConverter converterBean = new JwtAuthenticationConverter(); - GenericWebApplicationContext context = new GenericWebApplicationContext(); - context.registerBean("converterOne", JwtAuthenticationConverter.class, () -> converterBean); - context.registerBean("converterTwo", JwtAuthenticationConverter.class, () -> converterBean); - this.spring.context(context).autowire(); - new OAuth2ResourceServerConfigurer(context) - .jwt((jwt) -> assertThatExceptionOfType(NoUniqueBeanDefinitionException.class) - .isThrownBy(jwt::getJwtAuthenticationConverter)); - } - - @Test - public void getWhenCustomAuthenticationConverterThenUsed() throws Exception { - this.spring - .register(RestOperationsConfig.class, OpaqueTokenAuthenticationConverterConfig.class, BasicController.class) - .autowire(); - OpaqueTokenAuthenticationConverter authenticationConverter = bean(OpaqueTokenAuthenticationConverter.class); - given(authenticationConverter.convert(anyString(), any(OAuth2AuthenticatedPrincipal.class))) - .willReturn(new TestingAuthenticationToken("jdoe", null, Collections.emptyList())); - mockJsonRestOperations(json("Active")); - // @formatter:off - this.mvc.perform(get("/authenticated").with(bearerToken("token"))) - .andExpect(status().isOk()) - .andExpect(content().string("jdoe")); - // @formatter:on - verify(authenticationConverter).convert(any(), any()); - } - - @Test - public void getAuthenticationConverterWhenDuplicateConverterBeansAndAnotherOnTheDslThenTheDslOneIsUsed() { - AuthenticationConverter converter = mock(AuthenticationConverter.class); - AuthenticationConverter converterBean = mock(AuthenticationConverter.class); - GenericWebApplicationContext context = new GenericWebApplicationContext(); - context.registerBean("converterOne", AuthenticationConverter.class, () -> converterBean); - context.registerBean("converterTwo", AuthenticationConverter.class, () -> converterBean); - this.spring.context(context).autowire(); - OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context); - oauth2.authenticationConverter(converter); - assertThat(oauth2.getAuthenticationConverter()).isEqualTo(converter); - } - - @Test - public void getAuthenticationConverterWhenConverterBeanAndAnotherOnTheDslThenTheDslOneIsUsed() { - AuthenticationConverter converter = mock(AuthenticationConverter.class); - AuthenticationConverter converterBean = mock(AuthenticationConverter.class); - GenericWebApplicationContext context = new GenericWebApplicationContext(); - context.registerBean(AuthenticationConverter.class, () -> converterBean); - this.spring.context(context).autowire(); - OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context); - oauth2.authenticationConverter(converter); - assertThat(oauth2.getAuthenticationConverter()).isEqualTo(converter); - } - - @Test - public void getAuthenticationConverterWhenDuplicateConverterBeansThenWiringException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy( - () -> this.spring.register(MultipleAuthenticationConverterBeansConfig.class, JwtDecoderConfig.class) - .autowire()) - .withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class); - } - - @Test - public void getAuthenticationConverterWhenNoConverterSpecifiedThenTheDefaultIsUsed() { - ApplicationContext context = this.spring.context(new GenericWebApplicationContext()).getContext(); - OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context); - assertThat(oauth2.getAuthenticationConverter()).isInstanceOf(BearerTokenAuthenticationConverter.class); - } - - private static void registerMockBean(GenericApplicationContext context, String name, Class clazz) { - context.registerBean(name, clazz, () -> mock(clazz)); - } - - private static BearerTokenRequestPostProcessor bearerToken(String token) { - return new BearerTokenRequestPostProcessor(token); - } - - private static ResultMatcher invalidRequestHeader(String message) { - return header().string(HttpHeaders.WWW_AUTHENTICATE, - AllOf.allOf(new StringStartsWith("Bearer " + "error=\"invalid_request\", " + "error_description=\""), - new StringContains(message), - new StringContains(", " + "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""), - new StringEndsWith( - ", " + "resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\""))); - } - - private static ResultMatcher invalidTokenHeader(String message) { - return header().string(HttpHeaders.WWW_AUTHENTICATE, - AllOf.allOf(new StringStartsWith("Bearer " + "error=\"invalid_token\", " + "error_description=\""), - new StringContains(message), - new StringContains(", " + "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""), - new StringEndsWith( - ", " + "resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\""))); - } - - private static ResultMatcher insufficientScopeHeader() { - return header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer " + "error=\"insufficient_scope\"" - + ", error_description=\"The request requires higher privileges than provided by the access token.\"" - + ", error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""); - } - - private String jwkSet() { - return new JWKSet(new RSAKey.Builder(TestKeys.DEFAULT_PUBLIC_KEY).keyID("1").build()).toString(); - } - - private String jwtFromIssuer(String issuer) throws Exception { - Map claims = new HashMap<>(); - claims.put(JwtClaimNames.ISS, issuer); - claims.put(JwtClaimNames.SUB, "test-subject"); - claims.put("scope", "message:read"); - JWSObject jws = new JWSObject(new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("1").build(), - new Payload(new JSONObject(claims))); - jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY)); - return jws.serialize(); - } - - private void mockWebServer(String response) { - this.web.enqueue(new MockResponse().setResponseCode(200) - .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setBody(response)); - } - - private void mockRestOperations(String response) { - RestOperations rest = this.spring.getContext().getBean(RestOperations.class); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - ResponseEntity entity = new ResponseEntity<>(response, headers, HttpStatus.OK); - given(rest.exchange(any(RequestEntity.class), eq(String.class))).willReturn(entity); - } - - private void mockJwksRestOperations(String response) { - RestOperations rest = this.spring.getContext().getBean(RestOperations.class); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - ResponseEntity entity = new ResponseEntity<>(response, headers, HttpStatus.OK); - given(rest.exchange(any(RequestEntity.class), eq(String.class))).willReturn(entity); - } - - private void mockJsonRestOperations(String response) { - try { - RestOperations rest = this.spring.getContext().getBean(RestOperations.class); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - ResponseEntity> entity = new ResponseEntity<>(JSONObjectUtils.parse(response), headers, - HttpStatus.OK); - given(rest.exchange(any(RequestEntity.class), eq(new ParameterizedTypeReference>() { - }))).willReturn(entity); - } - catch (Exception ex) { - throw new IllegalArgumentException(ex); - } - } - - private T bean(Class beanClass) { - return this.spring.getContext().getBean(beanClass); - } - - private T verifyBean(Class beanClass) { - return verify(this.spring.getContext().getBean(beanClass)); - } - - private T verifyBean(Class beanClass, VerificationMode mode) { - return verify(this.spring.getContext().getBean(beanClass), mode); - } - - private String json(String name) throws IOException { - return resource(name + ".json"); - } - - private String jwks(String name) throws IOException { - return resource(name + ".jwks"); - } - - private String token(String name) throws IOException { - return resource(name + ".token"); - } - - private String resource(String suffix) throws IOException { - String name = this.getClass().getSimpleName() + "-" + suffix; - ClassPathResource resource = new ClassPathResource(name, this.getClass()); - try (BufferedReader reader = new BufferedReader(new FileReader(resource.getFile()))) { - return reader.lines().collect(Collectors.joining()); - } - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class DefaultConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class DefaultInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2) -> oauth2 - .jwt(withDefaults()) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class JwkSetUriConfig { - - @Value("${mockwebserver.url:https://example.org}") - String jwkSetUri; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - DefaultBearerTokenResolver defaultBearerTokenResolver = new DefaultBearerTokenResolver(); - defaultBearerTokenResolver.setAllowUriQueryParameter(true); - http - .authorizeHttpRequests((requests) -> requests - .requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .bearerTokenResolver(defaultBearerTokenResolver) - .jwt((jwt) -> jwt.jwkSetUri(this.jwkSetUri))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class JwkSetUriInLambdaConfig { - - @Value("${mockwebserver.url:https://example.org}") - String jwkSetUri; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2) -> oauth2 - .jwt((jwt) -> jwt - .jwkSetUri(this.jwkSetUri) - ) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class CsrfDisabledConfig { - - @Value("${mockwebserver.url:https://example.org}") - String jwkSetUri; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated()) - .csrf((csrf) -> csrf.disable()) - .oauth2ResourceServer((server) -> server - .jwt((jwt) -> jwt.jwkSetUri(this.jwkSetUri))); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class AnonymousDisabledConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .anonymous((anonymous) -> anonymous.disable()) - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - @EnableGlobalMethodSecurity(prePostEnabled = true) - static class MethodSecurityConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class JwtlessConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer(withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class RealmNameConfiguredOnEntryPoint { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .authenticationEntryPoint(authenticationEntryPoint()) - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - AuthenticationEntryPoint authenticationEntryPoint() { - BearerTokenAuthenticationEntryPoint entryPoint = new BearerTokenAuthenticationEntryPoint(); - entryPoint.setRealmName("myRealm"); - return entryPoint; - } - - } - - @Configuration - @EnableWebSecurity - static class RealmNameConfiguredOnAccessDeniedHandler { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().denyAll()) - .oauth2ResourceServer((server) -> server - .accessDeniedHandler(accessDeniedHandler()) - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - AccessDeniedHandler accessDeniedHandler() { - BearerTokenAccessDeniedHandler accessDeniedHandler = new BearerTokenAccessDeniedHandler(); - accessDeniedHandler.setRealmName("myRealm"); - return accessDeniedHandler; - } - - } - - @Configuration - @EnableWebSecurity - static class ExceptionHandlingAndResourceServerWithAccessDeniedHandlerConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().denyAll()) - .exceptionHandling((handling) -> handling - .defaultAccessDeniedHandlerFor(new AccessDeniedHandlerImpl(), (request) -> false)) - .httpBasic(withDefaults()) - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager( - // @formatter:off - org.springframework.security.core.userdetails.User.withDefaultPasswordEncoder() - .username("basic-user") - .password("basic-password") - .roles("USER") - .build()); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class JwtAuthenticationConverterConfiguredOnDsl { - - private final Converter jwtAuthenticationConverter = mock(Converter.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .jwt((jwt) -> jwt - .jwtAuthenticationConverter(getJwtAuthenticationConverter()))); - return http.build(); - // @formatter:on - } - - Converter getJwtAuthenticationConverter() { - return this.jwtAuthenticationConverter; - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class CustomAuthorityMappingConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .requestMatchers("/requires-read-scope").hasAuthority("message:read")) - .oauth2ResourceServer((server) -> server - .jwt((jwt) -> jwt - .jwtAuthenticationConverter(getJwtAuthenticationConverter()))); - return http.build(); - // @formatter:on - } - - Converter getJwtAuthenticationConverter() { - JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); - converter.setJwtGrantedAuthoritiesConverter( - (jwt) -> Collections.singletonList(new SimpleGrantedAuthority("message:read"))); - return converter; - } - - } - - @Configuration - @EnableWebSecurity - static class BasicAndResourceServerConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .httpBasic(withDefaults()) - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager( - // @formatter:off - org.springframework.security.core.userdetails.User.withDefaultPasswordEncoder() - .username("basic-user") - .password("basic-password") - .roles("USER") - .build()); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class FormAndResourceServerConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .formLogin(withDefaults()) - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class OAuth2LoginAndResourceServerConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authz) -> authz - .anyRequest().authenticated() - ) - .oauth2Login(withDefaults()) - .oauth2ResourceServer((oauth2) -> oauth2.jwt(withDefaults())); - return http.build(); - // @formatter:on - } - - @Bean - ClientRegistrationRepository clients() { - ClientRegistration registration = TestClientRegistrations.clientRegistration().build(); - return new InMemoryClientRegistrationRepository(registration); - } - - } - - @Configuration - @EnableWebSecurity - static class JwtHalfConfiguredConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); // missing key configuration, e.g. jwkSetUri - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class AlwaysSessionCreationConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .sessionManagement((management) -> management - .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)) - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class AllowBearerTokenInRequestBodyConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .bearerTokenResolver(allowRequestBody()) - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - private BearerTokenResolver allowRequestBody() { - DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); - resolver.setAllowFormEncodedBodyParameter(true); - return resolver; - } - - } - - @Configuration - @EnableWebSecurity - static class AllowBearerTokenAsQueryParameterConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - @Bean - BearerTokenResolver allowQueryParameter() { - DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); - resolver.setAllowUriQueryParameter(true); - return resolver; - } - - } - - @Configuration - @EnableWebSecurity - static class MultipleBearerTokenResolverBeansConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - @Bean - BearerTokenResolver resolverOne() { - DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); - resolver.setAllowUriQueryParameter(true); - return resolver; - } - - @Bean - BearerTokenResolver resolverTwo() { - DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); - resolver.setAllowFormEncodedBodyParameter(true); - return resolver; - } - - } - - @Configuration - @EnableWebSecurity - static class CustomAuthenticationDetailsSource { - - AuthenticationDetailsSource authenticationDetailsSource = mock( - AuthenticationDetailsSource.class); - - @Bean - SecurityFilterChain web(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2) -> oauth2 - .jwt(withDefaults()) - .withObjectPostProcessor(new ObjectPostProcessor() { - @Override - public BearerTokenAuthenticationFilter postProcess(BearerTokenAuthenticationFilter object) { - object.setAuthenticationDetailsSource(CustomAuthenticationDetailsSource.this.authenticationDetailsSource); - return object; - } - }) - ); - return http.build(); - } - - @Bean - AuthenticationDetailsSource authenticationDetailsSource() { - return this.authenticationDetailsSource; - } - } - - @Configuration - @EnableWebSecurity - static class CustomJwtDecoderOnDsl { - - JwtDecoder decoder = mock(JwtDecoder.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .jwt((jwt) -> jwt.decoder(decoder()))); - return http.build(); - // @formatter:on - } - - JwtDecoder decoder() { - return this.decoder; - } - - } - - @Configuration - @EnableWebSecurity - static class CustomJwtDecoderInLambdaOnDsl { - - JwtDecoder decoder = mock(JwtDecoder.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2) -> oauth2 - .jwt((jwt) -> jwt - .decoder(decoder()) - ) - ); - return http.build(); - // @formatter:on - } - - JwtDecoder decoder() { - return this.decoder; - } - - } - - @Configuration - @EnableWebSecurity - static class CustomJwtDecoderAsBean { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - @Bean - JwtDecoder decoder() { - return mock(JwtDecoder.class); - } - - } - - @Configuration - @EnableWebSecurity - static class JwtAuthenticationManagerConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .jwt((jwt) -> jwt - .authenticationManager(authenticationProvider()::authenticate))); - return http.build(); - // @formatter:on - } - - @Bean - AuthenticationProvider authenticationProvider() { - return mock(AuthenticationProvider.class); - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultAndJwtAuthenticationManagerConfig { - - AuthenticationManager defaultAuthenticationManager = mock(AuthenticationManager.class); - - AuthenticationManager jwtAuthenticationManager = mock(AuthenticationManager.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authenticationManager(this.defaultAuthenticationManager) - .authorizeHttpRequests((authz) -> authz - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2) -> oauth2 - .jwt((jwt) -> jwt - .authenticationManager(this.jwtAuthenticationManager) - ) - ); - return http.build(); - // @formatter:on - } - - AuthenticationManager defaultAuthenticationManager() { - return this.defaultAuthenticationManager; - } - - AuthenticationManager jwtAuthenticationManager() { - return this.jwtAuthenticationManager; - } - - } - - @Configuration - @EnableWebSecurity - static class CustomJwtValidatorConfig { - - @Autowired - NimbusJwtDecoder jwtDecoder; - - private final OAuth2TokenValidator jwtValidator = mock(OAuth2TokenValidator.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - this.jwtDecoder.setJwtValidator(this.jwtValidator); - // @formatter:off - http - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - OAuth2TokenValidator getJwtValidator() { - return this.jwtValidator; - } - - } - - @Configuration - @EnableWebSecurity - static class UnexpiredJwtClockSkewConfig { - - @Autowired - NimbusJwtDecoder jwtDecoder; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - Clock nearlyAnHourFromTokenExpiry = Clock.fixed(Instant.ofEpochMilli(4687181540000L), - ZoneId.systemDefault()); - JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofHours(1)); - jwtValidator.setClock(nearlyAnHourFromTokenExpiry); - this.jwtDecoder.setJwtValidator(jwtValidator); - // @formatter:off - http - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class ExpiredJwtClockSkewConfig { - - @Autowired - NimbusJwtDecoder jwtDecoder; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - Clock justOverOneHourAfterExpiry = Clock.fixed(Instant.ofEpochMilli(4687181595000L), - ZoneId.systemDefault()); - JwtTimestampValidator jwtValidator = new JwtTimestampValidator(Duration.ofHours(1)); - jwtValidator.setClock(justOverOneHourAfterExpiry); - this.jwtDecoder.setJwtValidator(jwtValidator); - // @formatter:off - http - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); - } - } - @Configuration - @EnableWebSecurity - static class SingleKeyConfig { - byte[] spec = Base64.getDecoder().decode( - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoXJ8OyOv/eRnce4akdan" + - "R4KYRfnC2zLV4uYNQpcFn6oHL0dj7D6kxQmsXoYgJV8ZVDn71KGmuLvolxsDncc2" + - "UrhyMBY6DVQVgMSVYaPCTgW76iYEKGgzTEw5IBRQL9w3SRJWd3VJTZZQjkXef48O" + - "cz06PGF3lhbz4t5UEZtdF4rIe7u+977QwHuh7yRPBQ3sII+cVoOUMgaXB9SHcGF2" + - "iZCtPzL/IffDUcfhLQteGebhW8A6eUHgpD5A1PQ+JCw/G7UOzZAjjDjtNM2eqm8j" + - "+Ms/gqnm4MiCZ4E+9pDN77CAAPVN7kuX6ejs9KBXpk01z48i9fORYk9u7rAkh1Hu" + - "QwIDAQAB"); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - @Bean - JwtDecoder decoder() throws Exception { - RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA") - .generatePublic(new X509EncodedKeySpec(this.spec)); - return NimbusJwtDecoder.withPublicKey(publicKey).build(); - } - - } - - @Configuration - @EnableWebSecurity - static class CustomAuthenticationEventPublisher { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - @Bean - JwtDecoder jwtDecoder() { - return mock(JwtDecoder.class); - } - - @Bean - AuthenticationEventPublisher authenticationEventPublisher() { - return mock(AuthenticationEventPublisher.class); - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class OpaqueTokenConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .opaqueToken(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class OpaqueTokenInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2) -> oauth2 - .opaqueToken(withDefaults()) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class OpaqueTokenAuthenticationManagerConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .opaqueToken((opaqueToken) -> opaqueToken - .authenticationManager(authenticationProvider()::authenticate))); - return http.build(); - // @formatter:on - } - - @Bean - AuthenticationProvider authenticationProvider() { - return mock(AuthenticationProvider.class); - } - - } - - @Configuration - @EnableWebSecurity - static class OpaqueTokenAuthenticationManagerInLambdaConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2) -> oauth2 - .opaqueToken((opaqueToken) -> opaqueToken - .authenticationManager(authenticationProvider()::authenticate) - ) - ); - return http.build(); - // @formatter:on - } - - @Bean - AuthenticationProvider authenticationProvider() { - return mock(AuthenticationProvider.class); - } - - } - - @Configuration - @EnableWebSecurity - static class DefaultAndOpaqueTokenAuthenticationManagerConfig { - - AuthenticationManager defaultAuthenticationManager = mock(AuthenticationManager.class); - - AuthenticationManager opaqueTokenAuthenticationManager = mock(AuthenticationManager.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authenticationManager(this.defaultAuthenticationManager) - .authorizeHttpRequests((authz) -> authz - .anyRequest().authenticated() - ) - .oauth2ResourceServer((oauth2) -> oauth2 - .opaqueToken((opaque) -> opaque - .authenticationManager(this.opaqueTokenAuthenticationManager) - ) - ); - return http.build(); - // @formatter:on - } - - AuthenticationManager defaultAuthenticationManager() { - return this.defaultAuthenticationManager; - } - - AuthenticationManager opaqueTokenAuthenticationManager() { - return this.opaqueTokenAuthenticationManager; - } - - } - - @Configuration - @EnableWebSecurity - static class OpaqueAndJwtConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults()) - .opaqueToken(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class OpaqueTokenHalfConfiguredConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .opaqueToken((opaqueToken) -> opaqueToken - .introspectionUri("https://idp.example.com"))); - return http.build(); // missing credentials - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class MultipleAuthenticationConverterBeansConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - @Bean - AuthenticationConverter authenticationConverterOne() { - DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); - resolver.setAllowUriQueryParameter(true); - BearerTokenAuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter(); - authenticationConverter.setBearerTokenResolver(resolver); - return authenticationConverter; - } - - @Bean - AuthenticationConverter authenticationConverterTwo() { - DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); - resolver.setAllowUriQueryParameter(true); - BearerTokenAuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter(); - authenticationConverter.setBearerTokenResolver(resolver); - return authenticationConverter; - } - - } - - @Configuration - @EnableWebSecurity - static class MultipleIssuersConfig { - - @Autowired - MockWebServer web; - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - String issuerOne = this.web.url("/issuerOne").toString(); - String issuerTwo = this.web.url("/issuerTwo").toString(); - JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = JwtIssuerAuthenticationManagerResolver - .fromTrustedIssuers(issuerOne, issuerTwo); - // @formatter:off - http - .oauth2ResourceServer((server) -> server - .authenticationManagerResolver(authenticationManagerResolver)) - .anonymous(AbstractHttpConfigurer::disable); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - static class AuthenticationManagerResolverPlusOtherConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .authenticationManagerResolver(mock(AuthenticationManagerResolver.class)) - .opaqueToken(Customizer.withDefaults())); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - static class OpaqueTokenAuthenticationConverterConfig { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .requestMatchers("/requires-read-scope").hasAuthority("SCOPE_message:read") - .anyRequest().authenticated()) - .oauth2ResourceServer((server) -> server - .opaqueToken((opaqueToken) -> opaqueToken - .authenticationConverter(authenticationConverter()))); - return http.build(); - // @formatter:on - } - - @Bean - OpaqueTokenAuthenticationConverter authenticationConverter() { - return mock(OpaqueTokenAuthenticationConverter.class); - } - - } - - @Configuration - static class JwtDecoderConfig { - - @Bean - JwtDecoder jwtDecoder() { - return mock(JwtDecoder.class); - } - - } - - @RestController - static class BasicController { - - @GetMapping("/") - String get() { - return "ok"; - } - - @PostMapping("/post") - String post() { - return "post"; - } - - @RequestMapping(value = "/authenticated", method = { RequestMethod.GET, RequestMethod.POST }) - String authenticated(Authentication authentication) { - return authentication.getName(); - } - - @GetMapping("/requires-read-scope") - String requiresReadScope(JwtAuthenticationToken token) { - return token.getAuthorities() - .stream() - .filter((ga) -> ga.getAuthority().startsWith("SCOPE_")) - .map(GrantedAuthority::getAuthority) - .collect(Collectors.toList()) - .toString(); - } - - @GetMapping("/ms-requires-read-scope") - @PreAuthorize("hasAuthority('SCOPE_message:read')") - String msRequiresReadScope(JwtAuthenticationToken token) { - return requiresReadScope(token); - } - - @GetMapping("/ms-deny") - @PreAuthorize("denyAll") - String deny() { - return "hmm, that's odd"; - } - - } - - @Configuration - static class WebServerConfig implements BeanPostProcessor, EnvironmentAware { - - private final MockWebServer server = new MockWebServer(); - - @PreDestroy - void shutdown() throws IOException { - this.server.shutdown(); - } - - @Override - public void setEnvironment(Environment environment) { - if (environment instanceof ConfigurableEnvironment) { - ((ConfigurableEnvironment) environment).getPropertySources() - .addFirst(new MockWebServerPropertySource()); - } - } - - @Bean - MockWebServer web() { - return this.server; - } - - private class MockWebServerPropertySource extends PropertySource { - - MockWebServerPropertySource() { - super("mockwebserver"); - } - - @Override - public Object getProperty(String name) { - if ("mockwebserver.url".equals(name)) { - return WebServerConfig.this.server.url("/.well-known/jwks.json").toString(); - } - else { - return null; - } - } - - } - - } - - @Configuration - static class RestOperationsConfig { - - RestOperations rest = mock(RestOperations.class); - - @Bean - RestOperations rest() { - return this.rest; - } - - @Bean - NimbusJwtDecoder jwtDecoder() { - return NimbusJwtDecoder.withJwkSetUri("https://example.org/.well-known/jwks.json") - .restOperations(this.rest) - .build(); - } - - @Bean - OpaqueTokenIntrospector tokenIntrospectionClient() { - return new SpringOpaqueTokenIntrospector("https://example.org/introspect", this.rest); - } - - } - - private static class BearerTokenRequestPostProcessor implements RequestPostProcessor { - - private boolean asRequestParameter; - - private String token; - - BearerTokenRequestPostProcessor(String token) { - this.token = token; - } - - BearerTokenRequestPostProcessor asParam() { - this.asRequestParameter = true; - return this; - } - - @Override - public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { - if (this.asRequestParameter) { - request.setParameter("access_token", this.token); - } - else { - request.addHeader("Authorization", "Bearer " + this.token); - } - return request; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurerTests.java deleted file mode 100644 index f12d40cfc36..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/ott/OneTimeTokenLoginConfigurerTests.java +++ /dev/null @@ -1,409 +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.annotation.web.configurers.ott; - -import java.io.IOException; -import java.time.Duration; -import java.time.Instant; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.hamcrest.Matchers; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.security.authentication.ott.DefaultOneTimeToken; -import org.springframework.security.authentication.ott.GenerateOneTimeTokenRequest; -import org.springframework.security.authentication.ott.OneTimeToken; -import org.springframework.security.authentication.ott.OneTimeTokenService; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; -import org.springframework.security.web.authentication.ott.GenerateOneTimeTokenRequestResolver; -import org.springframework.security.web.authentication.ott.OneTimeTokenGenerationSuccessHandler; -import org.springframework.security.web.authentication.ott.RedirectOneTimeTokenGenerationSuccessHandler; -import org.springframework.security.web.csrf.CsrfToken; -import org.springframework.security.web.csrf.DefaultCsrfToken; -import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; -import org.springframework.test.web.servlet.MockMvc; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatException; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; -import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@ExtendWith(SpringTestContextExtension.class) -public class OneTimeTokenLoginConfigurerTests { - - public SpringTestContext spring = new SpringTestContext(this); - - @Autowired(required = false) - MockMvc mvc; - - @Autowired(required = false) - private GenerateOneTimeTokenRequestResolver resolver; - - @Autowired(required = false) - private OneTimeTokenService tokenService; - - @Autowired(required = false) - private OneTimeTokenGenerationSuccessHandler tokenGenerationSuccessHandler; - - @Test - void oneTimeTokenWhenCorrectTokenThenCanAuthenticate() throws Exception { - this.spring.register(OneTimeTokenDefaultConfig.class).autowire(); - this.mvc.perform(post("/ott/generate").param("username", "user").with(csrf())) - .andExpectAll(status().isFound(), redirectedUrl("/login/ott")); - - String token = getLastToken().getTokenValue(); - - this.mvc.perform(post("/login/ott").param("token", token).with(csrf())) - .andExpectAll(status().isFound(), redirectedUrl("/"), authenticated()); - } - - @Test - void oneTimeTokenWhenDifferentAuthenticationUrlsThenCanAuthenticate() throws Exception { - this.spring.register(OneTimeTokenDifferentUrlsConfig.class).autowire(); - this.mvc.perform(post("/generateurl").param("username", "user").with(csrf())) - .andExpectAll(status().isFound(), redirectedUrl("/redirected")); - - String token = getLastToken().getTokenValue(); - - this.mvc.perform(post("/loginprocessingurl").param("token", token).with(csrf())) - .andExpectAll(status().isFound(), redirectedUrl("/authenticated"), authenticated()); - } - - @Test - void oneTimeTokenWhenCorrectTokenUsedTwiceThenSecondTimeFails() throws Exception { - this.spring.register(OneTimeTokenDefaultConfig.class).autowire(); - this.mvc.perform(post("/ott/generate").param("username", "user").with(csrf())) - .andExpectAll(status().isFound(), redirectedUrl("/login/ott")); - - String token = getLastToken().getTokenValue(); - - this.mvc.perform(post("/login/ott").param("token", token).with(csrf())) - .andExpectAll(status().isFound(), redirectedUrl("/"), authenticated()); - - this.mvc.perform(post("/login/ott").param("token", token).with(csrf())) - .andExpectAll(status().isFound(), redirectedUrl("/login?error"), unauthenticated()); - } - - @Test - void oneTimeTokenWhenWrongTokenThenAuthenticationFail() throws Exception { - this.spring.register(OneTimeTokenDefaultConfig.class).autowire(); - this.mvc.perform(post("/ott/generate").param("username", "user").with(csrf())) - .andExpectAll(status().isFound(), redirectedUrl("/login/ott")); - - String token = "wrong"; - - this.mvc.perform(post("/login/ott").param("token", token).with(csrf())) - .andExpectAll(status().isFound(), redirectedUrl("/login?error"), unauthenticated()); - } - - @Test - void oneTimeTokenWhenConfiguredThenServesCss() throws Exception { - this.spring.register(OneTimeTokenDefaultConfig.class).autowire(); - this.mvc.perform(get("/default-ui.css")) - .andExpect(status().isOk()) - .andExpect(content().string(Matchers.containsString("body {"))); - } - - @Test - void oneTimeTokenWhenConfiguredThenRendersRequestTokenForm() throws Exception { - this.spring.register(OneTimeTokenDefaultConfig.class).autowire(); - CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "BaseSpringSpec_CSRFTOKEN"); - String csrfAttributeName = HttpSessionCsrfTokenRepository.class.getName().concat(".CSRF_TOKEN"); - //@formatter:off - this.mvc.perform(get("/login").sessionAttr(csrfAttributeName, csrfToken)) - .andExpect((result) -> { - CsrfToken token = (CsrfToken) result.getRequest().getAttribute(CsrfToken.class.getName()); - assertThat(result.getResponse().getContentAsString()).isEqualTo( - """ - - - - - - - - Please sign in - - - -
- - - - -
- - """.formatted(token.getToken(), token.getToken())); - }); - //@formatter:on - } - - @Test - void oneTimeTokenWhenLoginPageConfiguredThenRedirects() throws Exception { - this.spring.register(OneTimeTokenLoginPageConfig.class).autowire(); - this.mvc.perform(get("/login")).andExpect(status().isFound()).andExpect(redirectedUrl("/custom-login")); - } - - @Test - void oneTimeTokenWhenNoTokenGenerationSuccessHandlerThenException() { - assertThatException() - .isThrownBy(() -> this.spring.register(OneTimeTokenNoGeneratedOttHandlerConfig.class).autowire()) - .havingRootCause() - .isInstanceOf(IllegalStateException.class) - .withMessage(""" - A OneTimeTokenGenerationSuccessHandler is required to enable oneTimeTokenLogin(). - Please provide it as a bean or pass it to the oneTimeTokenLogin() DSL. - """); - } - - @Test - void oneTimeTokenWhenCustomTokenExpirationTimeSetThenAuthenticate() throws Exception { - this.spring.register(OneTimeTokenConfigWithCustomImpls.class).autowire(); - GenerateOneTimeTokenRequest expectedGenerateRequest = new GenerateOneTimeTokenRequest("username-123", - Duration.ofMinutes(10)); - OneTimeToken ott = new DefaultOneTimeToken("token-123", expectedGenerateRequest.getUsername(), - Instant.now().plus(expectedGenerateRequest.getExpiresIn())); - given(this.resolver.resolve(any())).willReturn(expectedGenerateRequest); - given(this.tokenService.generate(expectedGenerateRequest)).willReturn(ott); - this.mvc.perform(post("/ott/generate").param("username", "user").with(csrf())); - - verify(this.resolver).resolve(any()); - verify(this.tokenService).generate(expectedGenerateRequest); - verify(this.tokenGenerationSuccessHandler).handle(any(), any(), eq(ott)); - } - - private OneTimeToken getLastToken() { - OneTimeToken lastToken = this.spring.getContext() - .getBean(TestOneTimeTokenGenerationSuccessHandler.class).lastToken; - return lastToken; - } - - @Configuration(proxyBeanMethods = false) - @EnableWebSecurity - @Import(UserDetailsServiceConfig.class) - static class OneTimeTokenConfigWithCustomImpls { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http, - GenerateOneTimeTokenRequestResolver ottRequestResolver, OneTimeTokenService ottTokenService, - OneTimeTokenGenerationSuccessHandler ottSuccessHandler) throws Exception { - - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .oneTimeTokenLogin((ott) -> ott - .generateRequestResolver(ottRequestResolver) - .tokenService(ottTokenService) - .tokenGenerationSuccessHandler(ottSuccessHandler) - ); - // @formatter:on - return http.build(); - } - - @Bean - GenerateOneTimeTokenRequestResolver generateOneTimeTokenRequestResolver() { - return mock(GenerateOneTimeTokenRequestResolver.class); - } - - @Bean - OneTimeTokenService ottService() { - return mock(OneTimeTokenService.class); - } - - @Bean - OneTimeTokenGenerationSuccessHandler ottSuccessHandler() { - return mock(OneTimeTokenGenerationSuccessHandler.class); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableWebSecurity - @Import(UserDetailsServiceConfig.class) - static class OneTimeTokenDefaultConfig { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http, - OneTimeTokenGenerationSuccessHandler ottSuccessHandler) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .oneTimeTokenLogin((ott) -> ott - .tokenGenerationSuccessHandler(ottSuccessHandler) - ); - // @formatter:on - return http.build(); - } - - @Bean - TestOneTimeTokenGenerationSuccessHandler ottSuccessHandler() { - return new TestOneTimeTokenGenerationSuccessHandler(); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableWebSecurity - @Import(UserDetailsServiceConfig.class) - static class OneTimeTokenLoginPageConfig { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http, - OneTimeTokenGenerationSuccessHandler ottSuccessHandler) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .oneTimeTokenLogin((ott) -> ott - .tokenGenerationSuccessHandler(ottSuccessHandler) - .loginPage("/custom-login") - ); - // @formatter:on - return http.build(); - } - - @Bean - TestOneTimeTokenGenerationSuccessHandler ottSuccessHandler() { - return new TestOneTimeTokenGenerationSuccessHandler(); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableWebSecurity - @Import(UserDetailsServiceConfig.class) - static class OneTimeTokenDifferentUrlsConfig { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http, - OneTimeTokenGenerationSuccessHandler ottSuccessHandler) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .oneTimeTokenLogin((ott) -> ott - .tokenGeneratingUrl("/generateurl") - .tokenGenerationSuccessHandler(ottSuccessHandler) - .loginProcessingUrl("/loginprocessingurl") - .successHandler(new SimpleUrlAuthenticationSuccessHandler("/authenticated")) - ); - // @formatter:on - return http.build(); - } - - @Bean - TestOneTimeTokenGenerationSuccessHandler ottSuccessHandler() { - return new TestOneTimeTokenGenerationSuccessHandler("/redirected"); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableWebSecurity - @Import(UserDetailsServiceConfig.class) - static class OneTimeTokenNoGeneratedOttHandlerConfig { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize - .anyRequest().authenticated() - ) - .oneTimeTokenLogin(Customizer.withDefaults()); - // @formatter:on - return http.build(); - } - - } - - static class TestOneTimeTokenGenerationSuccessHandler implements OneTimeTokenGenerationSuccessHandler { - - private OneTimeToken lastToken; - - private final OneTimeTokenGenerationSuccessHandler delegate; - - TestOneTimeTokenGenerationSuccessHandler() { - this.delegate = new RedirectOneTimeTokenGenerationSuccessHandler("/login/ott"); - } - - TestOneTimeTokenGenerationSuccessHandler(String redirectUrl) { - this.delegate = new RedirectOneTimeTokenGenerationSuccessHandler(redirectUrl); - } - - @Override - public void handle(HttpServletRequest request, HttpServletResponse response, OneTimeToken oneTimeToken) - throws IOException, ServletException { - this.lastToken = oneTimeToken; - this.delegate.handle(request, response, oneTimeToken); - } - - } - - @Configuration(proxyBeanMethods = false) - static class UserDetailsServiceConfig { - - @Bean - UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin()); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java deleted file mode 100644 index 5289bc9140a..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java +++ /dev/null @@ -1,800 +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.annotation.web.configurers.saml2; - -import java.io.IOException; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.util.Base64; -import java.util.Collections; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.instancio.internal.util.ReflectionUtils; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.core.xml.io.Marshaller; -import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.Response; -import org.w3c.dom.Element; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextChangedListener; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.saml2.core.OpenSamlInitializationService; -import org.springframework.security.saml2.core.Saml2ErrorCodes; -import org.springframework.security.saml2.core.Saml2Utils; -import org.springframework.security.saml2.core.TestSaml2X509Credentials; -import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; -import org.springframework.security.saml2.provider.service.authentication.OpenSaml5AuthenticationProvider; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; -import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects; -import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; -import org.springframework.security.saml2.provider.service.web.DefaultRelyingPartyRegistrationResolver; -import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver; -import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository; -import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationTokenConverter; -import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml5AuthenticationRequestResolver; -import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.context.HttpRequestResponseHolder; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.security.web.servlet.TestMockHttpServletRequests; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.util.UriComponents; -import org.springframework.web.util.UriComponentsBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.startsWith; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for different Java configuration for {@link Saml2LoginConfigurer} - */ -@ExtendWith(SpringTestContextExtension.class) -public class Saml2LoginConfigurerTests { - - static { - OpenSamlInitializationService.initialize(); - } - - private static final RelyingPartyRegistration registration = TestRelyingPartyRegistrations.noCredentials() - .signingX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartySigningCredential())) - .assertingPartyMetadata((party) -> party - .verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))) - .build(); - - private static String SIGNED_RESPONSE; - - private static final AuthenticationConverter AUTHENTICATION_CONVERTER = mock(AuthenticationConverter.class); - - @Autowired - private ConfigurableApplicationContext context; - - @Autowired - private FilterChainProxy springSecurityFilterChain; - - @Autowired - private RelyingPartyRegistrationRepository repository; - - @Autowired - SecurityContextRepository securityContextRepository; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired(required = false) - MockMvc mvc; - - private MockHttpServletRequest request; - - private MockHttpServletResponse response; - - private MockFilterChain filterChain; - - @BeforeAll - static void createResponse() throws Exception { - String destination = registration.getAssertionConsumerServiceLocation(); - String assertingPartyEntityId = registration.getAssertingPartyMetadata().getEntityId(); - String relyingPartyEntityId = registration.getEntityId(); - Response response = TestOpenSamlObjects.response(destination, assertingPartyEntityId); - Assertion assertion = TestOpenSamlObjects.assertion("test@saml.user", assertingPartyEntityId, - relyingPartyEntityId, destination); - response.getAssertions().add(assertion); - Response signed = TestOpenSamlObjects.signed(response, - registration.getSigningX509Credentials().iterator().next(), relyingPartyEntityId); - Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(signed); - Element element = marshaller.marshall(signed); - Class clazz = ReflectionUtils.loadClass("net.shibboleth.utilities.java.support.xml.SerializeSupport"); - if (clazz == null) { - clazz = ReflectionUtils.loadClass("net.shibboleth.shared.xml.SerializeSupport"); - } - String serialized = ReflectionTestUtils.invokeMethod(clazz, "nodeToString", element); - SIGNED_RESPONSE = Saml2Utils.samlEncode(serialized.getBytes(StandardCharsets.UTF_8)); - } - - @BeforeEach - public void setup() { - this.request = TestMockHttpServletRequests.post("/login/saml2/sso/test-rp").build(); - this.response = new MockHttpServletResponse(); - this.filterChain = new MockFilterChain(); - } - - @AfterEach - public void cleanup() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void saml2LoginWhenDefaultsThenSaml2AuthenticatedPrincipal() throws Exception { - this.spring.register(Saml2LoginConfig.class, ResourceController.class).autowire(); - // @formatter:off - MockHttpSession session = (MockHttpSession) this.mvc - .perform(post("/login/saml2/sso/registration-id") - .param("SAMLResponse", SIGNED_RESPONSE)) - .andExpect(redirectedUrl("/")).andReturn().getRequest().getSession(false); - this.mvc.perform(get("/").session(session)) - .andExpect(content().string("test@saml.user")); - // @formatter:on - } - - @Test - public void saml2LoginWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { - this.spring - .register(Saml2LoginConfig.class, SecurityContextChangedListenerConfig.class, ResourceController.class) - .autowire(); - // @formatter:off - MockHttpSession session = (MockHttpSession) this.mvc - .perform(post("/login/saml2/sso/registration-id") - .param("SAMLResponse", SIGNED_RESPONSE)) - .andExpect(redirectedUrl("/")).andReturn().getRequest().getSession(false); - this.mvc.perform(get("/").session(session)) - .andExpect(content().string("test@saml.user")); - // @formatter:on - SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); - verify(strategy, atLeastOnce()).getContext(); - SecurityContextChangedListener listener = this.spring.getContext() - .getBean(SecurityContextChangedListener.class); - verify(listener, times(2)).securityContextChanged(setAuthentication(Saml2Authentication.class)); - } - - @Test - public void saml2LoginWhenConfiguringAuthenticationManagerThenTheManagerIsUsed() throws Exception { - // setup application context - this.spring.register(Saml2LoginConfigWithCustomAuthenticationManager.class).autowire(); - performSaml2Login("ROLE_AUTH_MANAGER"); - } - - @Test - public void saml2LoginWhenDefaultAndSamlAuthenticationManagerThenSamlManagerIsUsed() throws Exception { - this.spring.register(Saml2LoginConfigWithDefaultAndCustomAuthenticationManager.class).autowire(); - performSaml2Login("ROLE_AUTH_MANAGER"); - } - - @Test - public void authenticationRequestWhenAuthenticationRequestResolverBeanThenUses() throws Exception { - this.spring.register(CustomAuthenticationRequestResolverBean.class).autowire(); - MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn(); - UriComponents components = UriComponentsBuilder.fromUriString(result.getResponse().getRedirectedUrl()).build(); - String samlRequest = components.getQueryParams().getFirst("SAMLRequest"); - String decoded = URLDecoder.decode(samlRequest, "UTF-8"); - String inflated = Saml2Utils.samlInflate(Saml2Utils.samlDecode(decoded)); - assertThat(inflated).contains("ForceAuthn=\"true\""); - } - - @Test - public void authenticationRequestWhenAuthenticationRequestResolverDslThenUses() throws Exception { - this.spring.register(CustomAuthenticationRequestResolverDsl.class).autowire(); - MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn(); - UriComponents components = UriComponentsBuilder.fromUriString(result.getResponse().getRedirectedUrl()).build(); - String samlRequest = components.getQueryParams().getFirst("SAMLRequest"); - String decoded = URLDecoder.decode(samlRequest, "UTF-8"); - String inflated = Saml2Utils.samlInflate(Saml2Utils.samlDecode(decoded)); - assertThat(inflated).contains("ForceAuthn=\"true\""); - } - - @Test - public void authenticateWhenCustomAuthenticationConverterThenUses() throws Exception { - this.spring.register(CustomAuthenticationConverter.class).autowire(); - RelyingPartyRegistration relyingPartyRegistration = this.repository.findByRegistrationId("registration-id"); - String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE)); - given(CustomAuthenticationConverter.authenticationConverter.convert(any(HttpServletRequest.class))) - .willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response)); - // @formatter:off - MockHttpServletRequestBuilder request = post("/login/saml2/sso/" + relyingPartyRegistration.getRegistrationId()) - .param("SAMLResponse", SIGNED_RESPONSE); - // @formatter:on - this.mvc.perform(request).andExpect(redirectedUrl("/")); - verify(CustomAuthenticationConverter.authenticationConverter).convert(any(HttpServletRequest.class)); - } - - @Test - public void authenticateWhenCustomAuthenticationConverterBeanThenUses() throws Exception { - this.spring.register(CustomAuthenticationConverterBean.class).autowire(); - Saml2AuthenticationTokenConverter authenticationConverter = this.spring.getContext() - .getBean(Saml2AuthenticationTokenConverter.class); - RelyingPartyRegistration relyingPartyRegistration = this.repository.findByRegistrationId("registration-id"); - String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE)); - given(authenticationConverter.convert(any(HttpServletRequest.class))) - .willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response)); - // @formatter:off - MockHttpServletRequestBuilder request = post("/login/saml2/sso/" + relyingPartyRegistration.getRegistrationId()) - .param("SAMLResponse", SIGNED_RESPONSE); - // @formatter:on - this.mvc.perform(request).andExpect(redirectedUrl("/")); - verify(authenticationConverter).convert(any(HttpServletRequest.class)); - } - - @Test - public void authenticateWithInvalidDeflatedSAMLResponseThenFailureHandlerUses() throws Exception { - this.spring.register(CustomAuthenticationFailureHandler.class).autowire(); - byte[] invalidDeflated = "invalid".getBytes(); - String encoded = Saml2Utils.samlEncode(invalidDeflated); - MockHttpServletRequestBuilder request = get("/login/saml2/sso/registration-id").queryParam("SAMLResponse", - encoded); - this.mvc.perform(request); - ArgumentCaptor captor = ArgumentCaptor - .forClass(Saml2AuthenticationException.class); - verify(CustomAuthenticationFailureHandler.authenticationFailureHandler) - .onAuthenticationFailure(any(HttpServletRequest.class), any(HttpServletResponse.class), captor.capture()); - Saml2AuthenticationException exception = captor.getValue(); - assertThat(exception.getSaml2Error().getErrorCode()).isEqualTo(Saml2ErrorCodes.INVALID_RESPONSE); - assertThat(exception.getSaml2Error().getDescription()).isEqualTo("Unable to inflate string"); - assertThat(exception).hasRootCauseInstanceOf(IOException.class); - } - - @Test - public void authenticationRequestWhenCustomAuthnRequestRepositoryThenUses() throws Exception { - this.spring.register(CustomAuthenticationRequestRepository.class).autowire(); - MockHttpServletRequestBuilder request = get("/saml2/authenticate/registration-id"); - this.mvc.perform(request).andExpect(status().isFound()); - Saml2AuthenticationRequestRepository repository = this.spring.getContext() - .getBean(Saml2AuthenticationRequestRepository.class); - verify(repository).saveAuthenticationRequest(any(AbstractSaml2AuthenticationRequest.class), - any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void authenticateWhenCustomAuthnRequestRepositoryThenUses() throws Exception { - this.spring.register(CustomAuthenticationRequestRepository.class).autowire(); - MockHttpServletRequestBuilder request = post("/login/saml2/sso/registration-id").param("SAMLResponse", - SIGNED_RESPONSE); - Saml2AuthenticationRequestRepository repository = this.spring.getContext() - .getBean(Saml2AuthenticationRequestRepository.class); - this.mvc.perform(request); - verify(repository).loadAuthenticationRequest(any(HttpServletRequest.class)); - verify(repository).removeAuthenticationRequest(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void authenticationRequestWhenCustomAuthenticationRequestUriRepositoryThenUses() throws Exception { - this.spring.register(CustomAuthenticationRequestUriCustomAuthenticationConverter.class).autowire(); - MockHttpServletRequestBuilder request = get("/custom/auth/registration-id"); - this.mvc.perform(request).andExpect(status().isFound()); - Saml2AuthenticationRequestRepository repository = this.spring.getContext() - .getBean(Saml2AuthenticationRequestRepository.class); - verify(repository).saveAuthenticationRequest(any(AbstractSaml2AuthenticationRequest.class), - any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void authenticationRequestWhenCustomAuthenticationRequestPathRepositoryThenUses() throws Exception { - this.spring.register(CustomAuthenticationRequestUriQuery.class).autowire(); - MockHttpServletRequestBuilder request = get("/custom/auth/sso"); - this.mvc.perform(request) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/custom/auth/sso?entityId=registration-id")); - request.queryParam("entityId", registration.getRegistrationId()); - MvcResult result = this.mvc.perform(request).andExpect(status().isFound()).andReturn(); - String redirectedUrl = result.getResponse().getRedirectedUrl(); - assertThat(redirectedUrl).startsWith(registration.getAssertingPartyMetadata().getSingleSignOnServiceLocation()); - } - - @Test - public void saml2LoginWhenLoginProcessingUrlWithoutRegistrationIdAndDefaultAuthenticationConverterThenAutowires() - throws Exception { - this.spring.register(CustomLoginProcessingUrlDefaultAuthenticationConverter.class).autowire(); - } - - @Test - public void authenticateWhenCustomLoginProcessingUrlAndCustomAuthenticationConverterThenAuthenticate() - throws Exception { - this.spring.register(CustomLoginProcessingUrlCustomAuthenticationConverter.class).autowire(); - RelyingPartyRegistration relyingPartyRegistration = this.repository.findByRegistrationId("registration-id"); - String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE)); - given(AUTHENTICATION_CONVERTER.convert(any(HttpServletRequest.class))) - .willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response)); - // @formatter:off - MockHttpServletRequestBuilder request = post("/my/custom/url").param("SAMLResponse", SIGNED_RESPONSE); - // @formatter:on - this.mvc.perform(request).andExpect(redirectedUrl("/")); - verify(AUTHENTICATION_CONVERTER).convert(any(HttpServletRequest.class)); - } - - @Test - public void authenticateWhenCustomLoginProcessingUrlAndSaml2AuthenticationTokenConverterBeanThenAuthenticate() - throws Exception { - this.spring.register(CustomLoginProcessingUrlSaml2AuthenticationTokenConverterBean.class).autowire(); - Saml2AuthenticationTokenConverter authenticationConverter = this.spring.getContext() - .getBean(Saml2AuthenticationTokenConverter.class); - RelyingPartyRegistration relyingPartyRegistration = this.repository.findByRegistrationId("registration-id"); - String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE)); - given(authenticationConverter.convert(any(HttpServletRequest.class))) - .willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response)); - // @formatter:off - MockHttpServletRequestBuilder request = post("/my/custom/url").param("SAMLResponse", SIGNED_RESPONSE); - // @formatter:on - this.mvc.perform(request).andExpect(redirectedUrl("/")); - verify(authenticationConverter).convert(any(HttpServletRequest.class)); - } - - // gh-11657 - @Test - public void getFaviconWhenDefaultConfigurationThenDoesNotSaveAuthnRequest() throws Exception { - this.spring.register(Saml2LoginConfig.class).autowire(); - this.mvc.perform(get("/favicon.ico").accept(MediaType.TEXT_HTML)) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); - this.mvc.perform(get("/").accept(MediaType.TEXT_HTML)) - .andExpect(status().isFound()) - .andExpect(header().string("Location", startsWith("/saml2/authenticate"))); - } - - @Test - public void saml2LoginWhenCustomAuthenticationProviderThenUses() throws Exception { - this.spring.register(CustomAuthenticationProviderConfig.class).autowire(); - AuthenticationProvider provider = this.spring.getContext().getBean(AuthenticationProvider.class); - this.mvc.perform(post("/login/saml2/sso/registration-id").param("SAMLResponse", SIGNED_RESPONSE)) - .andExpect(status().isFound()); - verify(provider).authenticate(any()); - } - - private void performSaml2Login(String expected) throws IOException, ServletException { - // setup authentication parameters - this.request.setRequestURI("/login/saml2/sso/registration-id"); - this.request.setParameter("SAMLResponse", - Base64.getEncoder().encodeToString("saml2-xml-response-object".getBytes())); - // perform test - this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain); - // assertions - Authentication authentication = this.securityContextRepository - .loadContext(new HttpRequestResponseHolder(this.request, this.response)) - .getAuthentication(); - assertThat(authentication).as("Expected a valid authentication object.").isNotNull(); - assertThat(authentication.getAuthorities()).hasSize(1); - assertThat(authentication.getAuthorities()).first() - .isInstanceOf(SimpleGrantedAuthority.class) - .hasToString(expected); - } - - private static AuthenticationManager getAuthenticationManagerMock(String role) { - return new AuthenticationManager() { - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - if (!supports(authentication.getClass())) { - throw new AuthenticationServiceException("not supported"); - } - return new Saml2Authentication(() -> "auth principal", "saml2 response", - Collections.singletonList(new SimpleGrantedAuthority(role))); - } - - public boolean supports(Class authentication) { - return authentication.isAssignableFrom(Saml2AuthenticationToken.class); - } - }; - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - @Import(Saml2LoginConfigBeans.class) - static class Saml2LoginConfig { - - @Bean - SecurityFilterChain web(HttpSecurity http) throws Exception { - http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .saml2Login(Customizer.withDefaults()); - - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class Saml2LoginConfigWithCustomAuthenticationManager { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.saml2Login((login) -> login.authenticationManager(getAuthenticationManagerMock("ROLE_AUTH_MANAGER"))); - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class Saml2LoginConfigWithDefaultAndCustomAuthenticationManager { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authenticationManager(getAuthenticationManagerMock("DEFAULT_AUTH_MANAGER")) - .saml2Login((saml) -> saml - .authenticationManager(getAuthenticationManagerMock("ROLE_AUTH_MANAGER")) - ); - return http.build(); - // @formatter:on - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class CustomAuthenticationFailureHandler { - - static final AuthenticationFailureHandler authenticationFailureHandler = mock( - AuthenticationFailureHandler.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()) - .saml2Login((saml2) -> saml2.failureHandler(authenticationFailureHandler)); - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class CustomAuthenticationRequestResolverBean { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authz) -> authz - .anyRequest().authenticated() - ) - .saml2Login(Customizer.withDefaults()); - // @formatter:on - - return http.build(); - } - - @Bean - Saml2AuthenticationRequestResolver authenticationRequestResolver( - RelyingPartyRegistrationRepository registrations) { - RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver( - registrations); - OpenSaml5AuthenticationRequestResolver delegate = new OpenSaml5AuthenticationRequestResolver( - registrationResolver); - delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true)); - return delegate; - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class CustomAuthenticationRequestResolverDsl { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http, RelyingPartyRegistrationRepository registrations) - throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authz) -> authz - .anyRequest().authenticated() - ) - .saml2Login((saml2) -> saml2 - .authenticationRequestResolver(authenticationRequestResolver(registrations)) - ); - // @formatter:on - - return http.build(); - } - - Saml2AuthenticationRequestResolver authenticationRequestResolver( - RelyingPartyRegistrationRepository registrations) { - RelyingPartyRegistrationResolver registrationResolver = new DefaultRelyingPartyRegistrationResolver( - registrations); - OpenSaml5AuthenticationRequestResolver delegate = new OpenSaml5AuthenticationRequestResolver( - registrationResolver); - delegate.setAuthnRequestCustomizer((parameters) -> parameters.getAuthnRequest().setForceAuthn(true)); - return delegate; - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class CustomAuthenticationConverter { - - static final AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class); - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()) - .saml2Login((saml2) -> saml2.authenticationConverter(authenticationConverter)); - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class CustomAuthenticationConverterBean { - - private final Saml2AuthenticationTokenConverter authenticationConverter = mock( - Saml2AuthenticationTokenConverter.class); - - @Bean - SecurityFilterChain app(HttpSecurity http) throws Exception { - http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .saml2Login(Customizer.withDefaults()); - return http.build(); - } - - @Bean - Saml2AuthenticationTokenConverter authenticationConverter() { - return this.authenticationConverter; - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class CustomAuthenticationRequestRepository { - - private final Saml2AuthenticationRequestRepository repository = mock( - Saml2AuthenticationRequestRepository.class); - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - http.authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()); - http.saml2Login(withDefaults()); - return http.build(); - } - - @Bean - Saml2AuthenticationRequestRepository authenticationRequestRepository() { - return this.repository; - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class CustomLoginProcessingUrlDefaultAuthenticationConverter { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()) - .saml2Login((saml2) -> saml2.loginProcessingUrl("/my/custom/url")); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class CustomAuthenticationRequestUriCustomAuthenticationConverter { - - private final Saml2AuthenticationRequestRepository repository = mock( - Saml2AuthenticationRequestRepository.class); - - @Bean - Saml2AuthenticationRequestRepository authenticationRequestRepository() { - return this.repository; - } - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()) - .saml2Login((saml2) -> saml2.authenticationRequestUri("/custom/auth/{registrationId}")); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class CustomAuthenticationRequestUriQuery { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .saml2Login((saml2) -> saml2.authenticationRequestUriQuery("/custom/auth/sso?entityId={registrationId}")); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class CustomLoginProcessingUrlCustomAuthenticationConverter { - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()) - .saml2Login((saml2) -> saml2 - .loginProcessingUrl("/my/custom/url") - .authenticationConverter(AUTHENTICATION_CONVERTER) - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class CustomLoginProcessingUrlSaml2AuthenticationTokenConverterBean { - - private final Saml2AuthenticationTokenConverter authenticationConverter = mock( - Saml2AuthenticationTokenConverter.class); - - @Bean - SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authz) -> authz.anyRequest().authenticated()) - .saml2Login((saml2) -> saml2.loginProcessingUrl("/my/custom/url")); - // @formatter:on - return http.build(); - } - - @Bean - Saml2AuthenticationTokenConverter authenticationTokenConverter() { - return this.authenticationConverter; - } - - } - - @Configuration - @EnableWebSecurity - @EnableWebMvc - @Import(Saml2LoginConfigBeans.class) - static class CustomAuthenticationProviderConfig { - - private final AuthenticationProvider provider = spy(new OpenSaml5AuthenticationProvider()); - - @Bean - SecurityFilterChain web(HttpSecurity http) throws Exception { - http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .saml2Login(Customizer.withDefaults()); - - return http.build(); - } - - @Bean - AuthenticationProvider provider() { - return this.provider; - } - - } - - static class Saml2LoginConfigBeans { - - @Bean - SecurityContextRepository securityContextRepository() { - return new HttpSessionSecurityContextRepository(); - } - - @Bean - RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() { - return spy(new InMemoryRelyingPartyRegistrationRepository(registration)); - } - - } - - @RestController - static class ResourceController { - - @GetMapping("/") - String user(@AuthenticationPrincipal Saml2AuthenticatedPrincipal principal) { - return principal.getName(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurerTests.java deleted file mode 100644 index aae4bd16caa..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurerTests.java +++ /dev/null @@ -1,801 +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.annotation.web.configurers.saml2; - -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.opensaml.xmlsec.signature.support.SignatureConstants; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.HttpMethod; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.ObjectPostProcessor; -import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.saml2.core.Saml2Utils; -import org.springframework.security.saml2.core.Saml2X509Credential; -import org.springframework.security.saml2.core.TestSaml2X509Credentials; -import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutValidatorResult; -import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; -import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository; -import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter; -import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository; -import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver; -import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter; -import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.logout.LogoutFilter; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import org.springframework.security.web.servlet.TestMockHttpServletRequests; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.RequestPostProcessor; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.mock; -import static org.mockito.BDDMockito.verify; -import static org.mockito.BDDMockito.verifyNoInteractions; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.spy; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher.pathPattern; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for different Java configuration for {@link Saml2LogoutConfigurer} - */ -@ExtendWith(SpringTestContextExtension.class) -public class Saml2LogoutConfigurerTests { - - @Autowired - private ConfigurableApplicationContext context; - - @Autowired - private RelyingPartyRegistrationRepository repository; - - private final Saml2LogoutRequestRepository logoutRequestRepository = new HttpSessionLogoutRequestRepository(); - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired(required = false) - MockMvc mvc; - - private Saml2Authentication user; - - String apLogoutRequest = "nZFBa4MwGIb/iuQeE2NTXFDLQAaC26Hrdtgt1dQFNMnyxdH9+zlboeyww275SN7nzcOX787jEH0qD9qaAiUxRZEyre206Qv0cnjAGdqVOchxYE40trdT2KuPSUGI5qQBcbkq0OSNsBI0CCNHBSK04vn+sREspsJ5G2xrBxRVc1AbGZa29xAcCEK8i9VZjm5QsfU9GZYWsoCJv5ShqK4K1Ow5p5LyU4aP6XaLN3cpw9mGctydjrxNaZt1XM5vASZVGwjShAIxyhJMU8z4gSWCM8GSmDH+hqLX1Xv+JLpaiiXsb+3+lpMAyv8IoVI6rEzQ4QvrLie3uBX+NMfr6l/waT6t0AumvI6/FlN+Aw=="; - - String apLogoutRequestSigAlg = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; - - String apLogoutRequestRelayState = "33591874-b123-4f2c-ab0d-2d0d84aa8b56"; - - String apLogoutRequestSignature = "oKqdzrmn2YAqXcwkow2lzRXr5PNHm0s/gWsRnaZYhC+Oq5ekK5uIKQYvtmNR94HJjDe1VRs+vVQCYivgdoTzBV2ZlffTXZmYsCsY9q4jbCWR6R5CbhU73/MkKQsPcyVvMhNYxnDYapIlxDsfoZNTboDEz3GM+HRoGRfl9emCXY0lPRYwqC4kpu7oMDBkafR0A09jPIxFuNpqlLPwUxL9m+DGkvDK3mFDN1xJcgZaK73HcuJe7Qh4huOrKNFetwc5EvqfiwgiWF6sfq9A+rZBfCIYo10NNLY7fNQAR2IqwcKtawHgTGWbeshRyFrwVYMR64EnClfxUHsHKf5kiZ2dlw=="; - - String apLogoutResponse = "fZHRa4MwEMb/Fcl7jEadGqplrAwK3Uvb9WFvZ4ydoInk4uj++1nXbmWMvhwcd9/3Jb9bLE99530oi63RBQn9gHhKS1O3+liQ1/0zzciyXCD0HR/ExhzN6LYKB6NReZNUo/ieFWS0WhjAFoWGXqFwUuweXzaC+4EYrHFGmo54K4Wu1eDmuHfnBhSM2cFXJ+iHTvnGHlk3x7DZmNlLGvHWq4Jstk0GUSjjiIZJI2lcpQnNeRLTAOo4fwCeQg3Trr6+cm/OqmnWVHECVGWQ0jgCSatsKvXUxhFvZF7xSYU4qrVGB9oVhAc8pEFEebLnkeBc8NyPePpGvMOV1/Q3cqEjZrG9hXKfCSAqe+ZAShio0q51n7StF+zW7gf9zoEb8U/7ZGrlHaAb1f0onLfFbpRSIRJWXkJ+bdm/Fy6/AA=="; - - String apLogoutResponseSigAlg = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; - - String apLogoutResponseRelayState = "8f63887a-ec7e-4149-b6a0-dd730017f315"; - - String apLogoutResponseSignature = "h2fDqSIBfmnkRHKDMY4IxkCXcI0w98ydNsnPmv1b7GTZCWLbJ+oxaP2yZNPw7wOWXTv86cTPwKLjx5halKy5C+hhWnT0haKhuMcUvHlsgAMBbJKLV+1afzL4O77cvAQJmMNRK7ugXGNV5PTEnd1U4voy134OgdD5XycYiFVRZOwP5H84eJ9xxlvqQwqDvZTcgiF/ZS4ioZgzgnIFcbagZQ12LWNh26OMaUpIW04kCeO6t2dUsxOL6nZWvNrX/Zx1sORIpu4doDUa1RYC8YnjZeQEzDqUVC/dBO/mbVJ/hbF9tD0jBUx7YIgoXpqsWK4TcCsvmlmhrJXvGxDyoAWu2Q=="; - - String rpLogoutRequest = "nZFBa4MwGIb/iuQeY6NlGtQykIHgdui6HXaLmrqAJlm+OLp/v0wrlB122CXkI3mfNw/JD5dpDD6FBalVgXZhhAKhOt1LNRTo5fSAU3Qoc+DTSA1r9KBndxQfswAX+KQCth4VaLaKaQ4SmOKTAOY69nz/2DAaRsxY7XSnRxRUPigVd0vbu3MGGCHchOLCJzOKUNuBjEsLWcDErmUoqKsCNcc+yc5tsudYpPwOJzHvcJv6pfdjEtNzl7XU3wWYRa3AceUKRCO6w1GM6f5EY0Ypo1lIk+gNBa+bt38kulqyJWxv7f6W4wDC/gih0hoslJPuC8s+J7e4Df7k43X1L/jsdxt0xZTX8dfHlN8="; - - String rpLogoutRequestId = "LRd49fb45a-e8a7-43ac-b8ac-d8a7432fc9b2"; - - String rpLogoutRequestRelayState = "8f63887a-ec7e-4149-b6a0-dd730017f315"; - - String rpLogoutRequestSignature = "h2fDqSIBfmnkRHKDMY4IxkCXcI0w98ydNsnPmv1b7GTZCWLbJ+oxaP2yZNPw7wOWXTv86cTPwKLjx5halKy5C+hhWnT0haKhuMcUvHlsgAMBbJKLV+1afzL4O77cvAQJmMNRK7ugXGNV5PTEnd1U4voy134OgdD5XycYiFVRZOwP5H84eJ9xxlvqQwqDvZTcgiF/ZS4ioZgzgnIFcbagZQ12LWNh26OMaUpIW04kCeO6t2dUsxOL6nZWvNrX/Zx1sORIpu4doDUa1RYC8YnjZeQEzDqUVC/dBO/mbVJ/hbF9tD0jBUx7YIgoXpqsWK4TcCsvmlmhrJXvGxDyoAWu2Q=="; - - private MockHttpServletRequest request; - - private MockHttpServletResponse response; - - @BeforeEach - public void setup() { - DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", - Collections.emptyMap()); - principal.setRelyingPartyRegistrationId("registration-id"); - this.user = new Saml2Authentication(principal, "response", AuthorityUtils.createAuthorityList("ROLE_USER")); - this.request = TestMockHttpServletRequests.post("/login/saml2/sso/test-rp").build(); - this.response = new MockHttpServletResponse(); - } - - @AfterEach - public void cleanup() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void logoutWhenDefaultsAndNotSaml2LoginThenDefaultLogout() throws Exception { - this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); - TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password"); - MvcResult result = this.mvc.perform(post("/logout").with(authentication(user)).with(csrf())) - .andExpect(status().isFound()) - .andReturn(); - String location = result.getResponse().getHeader("Location"); - LogoutHandler logoutHandler = this.spring.getContext().getBean(LogoutHandler.class); - assertThat(location).isEqualTo("/login?logout"); - verify(logoutHandler).logout(any(), any(), any()); - } - - @Test - public void saml2LogoutWhenDefaultsThenLogsOutAndSendsLogoutRequest() throws Exception { - this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); - MvcResult result = this.mvc.perform(post("/logout").with(authentication(this.user)).with(csrf())) - .andExpect(status().isFound()) - .andReturn(); - String location = result.getResponse().getHeader("Location"); - LogoutHandler logoutHandler = this.spring.getContext().getBean(LogoutHandler.class); - assertThat(location).startsWith("https://ap.example.org/logout/saml2/request"); - verify(logoutHandler).logout(any(), any(), any()); - } - - @Test - public void saml2LogoutWhenUnauthenticatedThenEntryPoint() throws Exception { - this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); - this.mvc.perform(post("/logout").with(csrf())) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - } - - @Test - public void saml2LogoutWhenMissingCsrfThen403() throws Exception { - this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); - this.mvc.perform(post("/logout").with(authentication(this.user))).andExpect(status().isForbidden()); - verifyNoInteractions(getBean(LogoutHandler.class)); - } - - @Test - public void saml2LogoutWhenGetThenDefaultLogoutPage() throws Exception { - this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); - MvcResult result = this.mvc.perform(get("/logout").with(authentication(this.user))) - .andExpect(status().isOk()) - .andReturn(); - assertThat(result.getResponse().getContentAsString()).contains("Are you sure you want to log out?"); - verifyNoInteractions(getBean(LogoutHandler.class)); - } - - @Test - public void saml2LogoutWhenPutOrDeleteThen404() throws Exception { - this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); - this.mvc.perform(put("/logout").with(authentication(this.user)).with(csrf())).andExpect(status().isNotFound()); - this.mvc.perform(delete("/logout").with(authentication(this.user)).with(csrf())) - .andExpect(status().isNotFound()); - verifyNoInteractions(this.spring.getContext().getBean(LogoutHandler.class)); - } - - @Test - public void saml2LogoutWhenNoRegistrationThen401() throws Exception { - this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); - DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", - Collections.emptyMap()); - principal.setRelyingPartyRegistrationId("wrong"); - Saml2Authentication authentication = new Saml2Authentication(principal, "response", - AuthorityUtils.createAuthorityList("ROLE_USER")); - this.mvc.perform(post("/logout").with(authentication(authentication)).with(csrf())) - .andExpect(status().isUnauthorized()); - } - - @Test - public void saml2LogoutWhenCsrfDisabledAndNoAuthenticationThenFinalRedirect() throws Exception { - this.spring.register(Saml2LogoutCsrfDisabledConfig.class).autowire(); - this.mvc.perform(post("/logout")); - LogoutSuccessHandler logoutSuccessHandler = this.spring.getContext().getBean(LogoutSuccessHandler.class); - verify(logoutSuccessHandler).onLogoutSuccess(any(), any(), any()); - } - - @Test - public void saml2LogoutWhenCustomLogoutRequestResolverThenUses() throws Exception { - this.spring.register(Saml2LogoutComponentsConfig.class).autowire(); - RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id"); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) - .samlRequest(this.rpLogoutRequest) - .id(this.rpLogoutRequestId) - .relayState(this.rpLogoutRequestRelayState) - .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) - .build(); - given(getBean(Saml2LogoutRequestResolver.class).resolve(any(), any())).willReturn(logoutRequest); - this.mvc.perform(post("/logout").with(authentication(this.user)).with(csrf())); - verify(getBean(Saml2LogoutRequestResolver.class)).resolve(any(), any()); - } - - @Test - public void saml2LogoutRequestWhenDefaultsThenLogsOutAndSendsLogoutResponse() throws Exception { - this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); - DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", - Collections.emptyMap()); - principal.setRelyingPartyRegistrationId("get"); - Saml2Authentication user = new Saml2Authentication(principal, "response", - AuthorityUtils.createAuthorityList("ROLE_USER")); - MvcResult result = this.mvc - .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) - .param("RelayState", this.apLogoutRequestRelayState) - .param("SigAlg", this.apLogoutRequestSigAlg) - .param("Signature", this.apLogoutRequestSignature) - .with(samlQueryString()) - .with(authentication(user))) - .andExpect(status().isFound()) - .andReturn(); - String location = result.getResponse().getHeader("Location"); - assertThat(location).startsWith("https://ap.example.org/logout/saml2/response"); - verify(getBean(LogoutHandler.class)).logout(any(), any(), any()); - } - - @Test - public void saml2LogoutRequestWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { - this.spring.register(Saml2LogoutDefaultsConfig.class, SecurityContextChangedListenerConfig.class).autowire(); - DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", - Collections.emptyMap()); - principal.setRelyingPartyRegistrationId("get"); - Saml2Authentication user = new Saml2Authentication(principal, "response", - AuthorityUtils.createAuthorityList("ROLE_USER")); - MvcResult result = this.mvc - .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) - .param("RelayState", this.apLogoutRequestRelayState) - .param("SigAlg", this.apLogoutRequestSigAlg) - .param("Signature", this.apLogoutRequestSignature) - .with(samlQueryString()) - .with(authentication(user))) - .andExpect(status().isFound()) - .andReturn(); - String location = result.getResponse().getHeader("Location"); - assertThat(location).startsWith("https://ap.example.org/logout/saml2/response"); - verify(getBean(LogoutHandler.class)).logout(any(), any(), any()); - verify(getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); - } - - // gh-11235 - @Test - public void saml2LogoutRequestWhenLowercaseEncodingThenLogsOutAndSendsLogoutResponse() throws Exception { - this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); - String apLogoutRequest = "nZFNa4QwEIb/iuQeP6K7dYO6FKQg2B622x56G3WwgiY2E8v239fqCksPPfSWIXmfNw+THC9D73yi\r\n" - + "oU6rlAWuzxxUtW461abs5fzAY3bMEoKhF6Msdasne8KPCck6c1KRXK9SNhklNVBHUsGAJG0tn+8f\r\n" - + "SylcX45GW13rnjn5HOwU2KXt3dqRpOeZ0cULDGOPrjat1y8t3gL2zFrGnCJPWXkKcR8KCHY8xmrP\r\n" - + "Iz868OpOVLwO4wohggagmd8STVgosqBsyoQvBPd3XITnIJaRL8PYjcThjTmvm/f8SXa1lEvY3Nr9\r\n" - + "LQdEaH6EWAYjR2U7+8W7JvFucRv8aY4X+b/g03zaoCsmu46/FpN9Aw=="; - String apLogoutRequestRelayState = "d118dbd5-3853-4268-b3e5-c40fc033fa2f"; - String apLogoutRequestSignature = "VZ7rWa5u3hIX60fAQs/gBQZWDP2BAIlCMMrNrTHafoKKj0uXWnuITYLuL8NdsWmyQN0+fqWW4X05+BqiLpL80jHLmQR5RVqqL1EtVv1SpPUna938lgz2sOliuYmfQNj4Bmd+Z5G1K6QhbVrtfb7TQHURjUafzfRm8+jGz3dPjVBrn/rD/umfGoSn6RuWngugcMNL4U0A+JcEh1NSfSYNVz7y+MqlW1UhX2kF86rm97ERCrxay7Gh/bI2f3fJPJ1r+EyLjzrDUkqw5cva3rVlFgEQouMVu35lUJn7SFompW8oTxkI23oc/t+AGZqaBupNITNdjyGCBpfukZ69EZrj8g=="; - DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", - Collections.emptyMap()); - principal.setRelyingPartyRegistrationId("get"); - Saml2Authentication user = new Saml2Authentication(principal, "response", - AuthorityUtils.createAuthorityList("ROLE_USER")); - MvcResult result = this.mvc - .perform(get("/logout/saml2/slo").param("SAMLRequest", apLogoutRequest) - .param("RelayState", apLogoutRequestRelayState) - .param("SigAlg", this.apLogoutRequestSigAlg) - .param("Signature", apLogoutRequestSignature) - .with(new SamlQueryStringRequestPostProcessor(true)) - .with(authentication(user))) - .andExpect(status().isFound()) - .andReturn(); - String location = result.getResponse().getHeader("Location"); - assertThat(location).startsWith("https://ap.example.org/logout/saml2/response"); - verify(getBean(LogoutHandler.class)).logout(any(), any(), any()); - } - - // gh-12346 - @Test - public void saml2LogoutRequestWhenLowercaseEncodingAndDifferentQueryParamOrderThenLogsOutAndSendsLogoutResponse() - throws Exception { - this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); - String apLogoutRequest = "nZFNa4QwEIb/iuQeP6K7dYO6FKQg2B622x56G3WwgiY2E8v239fqCksPPfSWIXmfNw+THC9D73yi\r\n" - + "oU6rlAWuzxxUtW461abs5fzAY3bMEoKhF6Msdasne8KPCck6c1KRXK9SNhklNVBHUsGAJG0tn+8f\r\n" - + "SylcX45GW13rnjn5HOwU2KXt3dqRpOeZ0cULDGOPrjat1y8t3gL2zFrGnCJPWXkKcR8KCHY8xmrP\r\n" - + "Iz868OpOVLwO4wohggagmd8STVgosqBsyoQvBPd3XITnIJaRL8PYjcThjTmvm/f8SXa1lEvY3Nr9\r\n" - + "LQdEaH6EWAYjR2U7+8W7JvFucRv8aY4X+b/g03zaoCsmu46/FpN9Aw=="; - String apLogoutRequestRelayState = "d118dbd5-3853-4268-b3e5-c40fc033fa2f"; - String apLogoutRequestSignature = "VZ7rWa5u3hIX60fAQs/gBQZWDP2BAIlCMMrNrTHafoKKj0uXWnuITYLuL8NdsWmyQN0+fqWW4X05+BqiLpL80jHLmQR5RVqqL1EtVv1SpPUna938lgz2sOliuYmfQNj4Bmd+Z5G1K6QhbVrtfb7TQHURjUafzfRm8+jGz3dPjVBrn/rD/umfGoSn6RuWngugcMNL4U0A+JcEh1NSfSYNVz7y+MqlW1UhX2kF86rm97ERCrxay7Gh/bI2f3fJPJ1r+EyLjzrDUkqw5cva3rVlFgEQouMVu35lUJn7SFompW8oTxkI23oc/t+AGZqaBupNITNdjyGCBpfukZ69EZrj8g=="; - DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", - Collections.emptyMap()); - principal.setRelyingPartyRegistrationId("get"); - Saml2Authentication user = new Saml2Authentication(principal, "response", - AuthorityUtils.createAuthorityList("ROLE_USER")); - MvcResult result = this.mvc - .perform(get("/logout/saml2/slo").param("SAMLRequest", apLogoutRequest) - .param("SigAlg", this.apLogoutRequestSigAlg) - .param("RelayState", apLogoutRequestRelayState) - .param("Signature", apLogoutRequestSignature) - .with(new SamlQueryStringRequestPostProcessor(true)) - .with(authentication(user))) - .andExpect(status().isFound()) - .andReturn(); - String location = result.getResponse().getHeader("Location"); - assertThat(location).startsWith("https://ap.example.org/logout/saml2/response"); - verify(getBean(LogoutHandler.class)).logout(any(), any(), any()); - } - - @Test - public void saml2LogoutRequestWhenNoRegistrationThen400() throws Exception { - this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); - DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", - Collections.emptyMap()); - principal.setRelyingPartyRegistrationId("wrong"); - Saml2Authentication user = new Saml2Authentication(principal, "response", - AuthorityUtils.createAuthorityList("ROLE_USER")); - this.mvc - .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) - .param("RelayState", this.apLogoutRequestRelayState) - .param("SigAlg", this.apLogoutRequestSigAlg) - .param("Signature", this.apLogoutRequestSignature) - .with(authentication(user))) - .andExpect(status().isBadRequest()); - verifyNoInteractions(getBean(LogoutHandler.class)); - } - - @Test - public void saml2LogoutRequestWhenInvalidSamlRequestThen302Redirect() throws Exception { - this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); - this.mvc - .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) - .param("RelayState", this.apLogoutRequestRelayState) - .param("SigAlg", this.apLogoutRequestSigAlg) - .with(authentication(this.user))) - .andExpect(status().isFound()); - verifyNoInteractions(getBean(LogoutHandler.class)); - } - - @Test - public void saml2LogoutRequestWhenCustomLogoutRequestHandlerThenUses() throws Exception { - this.spring.register(Saml2LogoutComponentsConfig.class).autowire(); - RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id"); - LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration); - logoutRequest.setIssueInstant(Instant.now()); - given(getBean(Saml2LogoutRequestValidator.class).validate(any())) - .willReturn(Saml2LogoutValidatorResult.success()); - Saml2LogoutResponse logoutResponse = Saml2LogoutResponse.withRelyingPartyRegistration(registration).build(); - given(getBean(Saml2LogoutResponseResolver.class).resolve(any(), any())).willReturn(logoutResponse); - this.mvc.perform(post("/logout/saml2/slo").param("SAMLRequest", "samlRequest").with(authentication(this.user))) - .andReturn(); - verify(getBean(Saml2LogoutRequestValidator.class)).validate(any()); - verify(getBean(Saml2LogoutResponseResolver.class)).resolve(any(), any()); - } - - @Test - public void saml2LogoutResponseWhenDefaultsThenRedirects() throws Exception { - this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); - RelyingPartyRegistration registration = this.repository.findByRegistrationId("get"); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) - .samlRequest(this.rpLogoutRequest) - .id(this.rpLogoutRequestId) - .relayState(this.rpLogoutRequestRelayState) - .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) - .build(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest, this.request, this.response); - this.request.setParameter("RelayState", logoutRequest.getRelayState()); - assertThat(this.logoutRequestRepository.loadLogoutRequest(this.request)).isNotNull(); - this.mvc - .perform(get("/logout/saml2/slo").session(((MockHttpSession) this.request.getSession())) - .param("SAMLResponse", this.apLogoutResponse) - .param("RelayState", this.apLogoutResponseRelayState) - .param("SigAlg", this.apLogoutResponseSigAlg) - .param("Signature", this.apLogoutResponseSignature) - .with(samlQueryString())) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - verifyNoInteractions(getBean(LogoutHandler.class)); - assertThat(this.logoutRequestRepository.loadLogoutRequest(this.request)).isNull(); - } - - @Test - public void saml2LogoutResponseWhenInvalidSamlResponseThen401() throws Exception { - this.spring.register(Saml2LogoutDefaultsConfig.class).autowire(); - RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id"); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) - .samlRequest(this.rpLogoutRequest) - .id(this.rpLogoutRequestId) - .relayState(this.rpLogoutRequestRelayState) - .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) - .build(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest, this.request, this.response); - String deflatedApLogoutResponse = Saml2Utils.samlEncode( - Saml2Utils.samlInflate(Saml2Utils.samlDecode(this.apLogoutResponse)).getBytes(StandardCharsets.UTF_8)); - this.mvc - .perform(post("/logout/saml2/slo").session((MockHttpSession) this.request.getSession()) - .param("SAMLResponse", deflatedApLogoutResponse) - .param("RelayState", this.rpLogoutRequestRelayState) - .param("SigAlg", this.apLogoutRequestSigAlg) - .param("Signature", this.apLogoutResponseSignature) - .with(samlQueryString())) - .andExpect(status().reason(containsString("invalid_signature"))) - .andExpect(status().isUnauthorized()); - verifyNoInteractions(getBean(LogoutHandler.class)); - } - - @Test - public void saml2LogoutResponseWhenCustomLogoutResponseHandlerThenUses() throws Exception { - this.spring.register(Saml2LogoutComponentsConfig.class).autowire(); - RelyingPartyRegistration registration = this.repository.findByRegistrationId("get"); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) - .samlRequest(this.rpLogoutRequest) - .id(this.rpLogoutRequestId) - .relayState(this.rpLogoutRequestRelayState) - .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) - .build(); - given(getBean(Saml2LogoutRequestRepository.class).removeLogoutRequest(any(), any())).willReturn(logoutRequest); - given(getBean(Saml2LogoutResponseValidator.class).validate(any())) - .willReturn(Saml2LogoutValidatorResult.success()); - this.mvc.perform(get("/logout/saml2/slo").param("SAMLResponse", "samlResponse")).andReturn(); - verify(getBean(Saml2LogoutResponseValidator.class)).validate(any()); - } - - // gh-11363 - @Test - public void saml2LogoutWhenCustomLogoutRequestRepositoryThenUses() throws Exception { - this.spring.register(Saml2LogoutComponentsConfig.class).autowire(); - RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id"); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) - .samlRequest(this.rpLogoutRequest) - .id(this.rpLogoutRequestId) - .relayState(this.rpLogoutRequestRelayState) - .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) - .build(); - given(getBean(Saml2LogoutRequestResolver.class).resolve(any(), any())).willReturn(logoutRequest); - this.mvc.perform(post("/logout").with(authentication(this.user)).with(csrf())); - verify(getBean(Saml2LogoutRequestRepository.class)).saveLogoutRequest(eq(logoutRequest), any(), any()); - } - - @Test - public void saml2LogoutWhenLogoutGetThenLogsOutAndSendsLogoutRequest() throws Exception { - this.spring.register(Saml2LogoutWithHttpGet.class).autowire(); - MvcResult result = this.mvc.perform(get("/logout").with(authentication(this.user))) - .andExpect(status().isFound()) - .andReturn(); - String location = result.getResponse().getHeader("Location"); - LogoutHandler logoutHandler = this.spring.getContext().getBean(LogoutHandler.class); - assertThat(location).startsWith("https://ap.example.org/logout/saml2/request"); - verify(logoutHandler).logout(any(), any(), any()); - } - - @Test - public void saml2LogoutWhenSaml2LogoutRequestFilterPostProcessedThenUses() { - - Saml2DefaultsWithObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - this.spring.register(Saml2DefaultsWithObjectPostProcessorConfig.class).autowire(); - verify(Saml2DefaultsWithObjectPostProcessorConfig.objectPostProcessor) - .postProcess(any(Saml2LogoutRequestFilter.class)); - - } - - @Test - public void saml2LogoutWhenSaml2LogoutResponseFilterPostProcessedThenUses() { - - Saml2DefaultsWithObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - this.spring.register(Saml2DefaultsWithObjectPostProcessorConfig.class).autowire(); - verify(Saml2DefaultsWithObjectPostProcessorConfig.objectPostProcessor) - .postProcess(any(Saml2LogoutResponseFilter.class)); - - } - - @Test - public void saml2LogoutWhenLogoutFilterPostProcessedThenUses() { - - Saml2DefaultsWithObjectPostProcessorConfig.objectPostProcessor = spy(ReflectingObjectPostProcessor.class); - this.spring.register(Saml2DefaultsWithObjectPostProcessorConfig.class).autowire(); - verify(Saml2DefaultsWithObjectPostProcessorConfig.objectPostProcessor, atLeastOnce()) - .postProcess(any(LogoutFilter.class)); - - } - - private T getBean(Class clazz) { - return this.spring.getContext().getBean(clazz); - } - - private SamlQueryStringRequestPostProcessor samlQueryString() { - return new SamlQueryStringRequestPostProcessor(); - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class Saml2LogoutDefaultsConfig { - - LogoutHandler mockLogoutHandler = mock(LogoutHandler.class); - - @Bean - SecurityFilterChain web(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .logout((logout) -> logout.addLogoutHandler(this.mockLogoutHandler)) - .saml2Login(withDefaults()) - .saml2Logout(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - LogoutHandler logoutHandler() { - return this.mockLogoutHandler; - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class Saml2LogoutCsrfDisabledConfig { - - LogoutSuccessHandler mockLogoutSuccessHandler = mock(LogoutSuccessHandler.class); - - @Bean - SecurityFilterChain web(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .logout((logout) -> logout.logoutSuccessHandler(this.mockLogoutSuccessHandler)) - .saml2Login(withDefaults()) - .saml2Logout(withDefaults()) - .csrf((csrf) -> csrf.disable()); - return http.build(); - // @formatter:on - } - - @Bean - LogoutSuccessHandler logoutSuccessHandler() { - return this.mockLogoutSuccessHandler; - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class Saml2LogoutWithHttpGet { - - LogoutHandler mockLogoutHandler = mock(LogoutHandler.class); - - @Bean - SecurityFilterChain web(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .logout((logout) -> logout.addLogoutHandler(this.mockLogoutHandler)) - .saml2Login(withDefaults()) - .saml2Logout((saml2) -> saml2.addObjectPostProcessor(new ObjectPostProcessor() { - @Override - public O postProcess(O filter) { - filter.setLogoutRequestMatcher(pathPattern(HttpMethod.GET, "/logout")); - return filter; - } - })); - return http.build(); - // @formatter:on - } - - @Bean - LogoutHandler logoutHandler() { - return this.mockLogoutHandler; - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class Saml2DefaultsWithObjectPostProcessorConfig { - - static ObjectPostProcessor objectPostProcessor; - - @Bean - SecurityFilterChain web(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .saml2Login(withDefaults()) - .saml2Logout(withDefaults()); - return http.build(); - // @formatter:on - } - - @Bean - static ObjectPostProcessor objectPostProcessor() { - return objectPostProcessor; - } - - } - - @Configuration - @EnableWebSecurity - @Import(Saml2LoginConfigBeans.class) - static class Saml2LogoutComponentsConfig { - - Saml2LogoutRequestRepository logoutRequestRepository = mock(Saml2LogoutRequestRepository.class); - - Saml2LogoutRequestValidator logoutRequestValidator = mock(Saml2LogoutRequestValidator.class); - - Saml2LogoutRequestResolver logoutRequestResolver = mock(Saml2LogoutRequestResolver.class); - - Saml2LogoutResponseValidator logoutResponseValidator = mock(Saml2LogoutResponseValidator.class); - - Saml2LogoutResponseResolver logoutResponseResolver = mock(Saml2LogoutResponseResolver.class); - - @Bean - SecurityFilterChain web(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .saml2Login(withDefaults()) - .saml2Logout((logout) -> logout - .logoutRequest((request) -> request - .logoutRequestRepository(this.logoutRequestRepository) - .logoutRequestValidator(this.logoutRequestValidator) - .logoutRequestResolver(this.logoutRequestResolver) - ) - .logoutResponse((response) -> response - .logoutResponseValidator(this.logoutResponseValidator) - .logoutResponseResolver(this.logoutResponseResolver) - ) - ); - return http.build(); - // @formatter:on - } - - @Bean - Saml2LogoutRequestRepository logoutRequestRepository() { - return this.logoutRequestRepository; - } - - @Bean - Saml2LogoutRequestValidator logoutRequestAuthenticator() { - return this.logoutRequestValidator; - } - - @Bean - Saml2LogoutRequestResolver logoutRequestResolver() { - return this.logoutRequestResolver; - } - - @Bean - Saml2LogoutResponseValidator logoutResponseAuthenticator() { - return this.logoutResponseValidator; - } - - @Bean - Saml2LogoutResponseResolver logoutResponseResolver() { - return this.logoutResponseResolver; - } - - } - - static class Saml2LoginConfigBeans { - - @Bean - RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() { - Saml2X509Credential signing = TestSaml2X509Credentials.assertingPartySigningCredential(); - Saml2X509Credential verification = TestSaml2X509Credentials.relyingPartyVerifyingCredential(); - RelyingPartyRegistration.Builder withCreds = TestRelyingPartyRegistrations.noCredentials() - .signingX509Credentials(credential(signing)) - .assertingPartyMetadata((party) -> party.verificationX509Credentials(credential(verification))); - RelyingPartyRegistration post = withCreds.build(); - RelyingPartyRegistration get = withCreds.registrationId("get") - .singleLogoutServiceBinding(Saml2MessageBinding.REDIRECT) - .build(); - RelyingPartyRegistration ap = withCreds.registrationId("ap") - .entityId("ap-entity-id") - .assertingPartyMetadata( - (party) -> party.singleLogoutServiceLocation("https://rp.example.org/logout/saml2/request") - .singleLogoutServiceResponseLocation("https://rp.example.org/logout/saml2/response")) - .build(); - - return new InMemoryRelyingPartyRegistrationRepository(ap, get, post); - } - - private Consumer> credential(Saml2X509Credential credential) { - return (credentials) -> credentials.add(credential); - } - - } - - static class ReflectingObjectPostProcessor implements ObjectPostProcessor { - - @Override - public O postProcess(O object) { - return object; - } - - } - - static class SamlQueryStringRequestPostProcessor implements RequestPostProcessor { - - private Function urlEncodingPostProcessor = Function.identity(); - - SamlQueryStringRequestPostProcessor() { - this(false); - } - - SamlQueryStringRequestPostProcessor(boolean lowercased) { - if (lowercased) { - Pattern encoding = Pattern.compile("%\\d[A-Fa-f]"); - this.urlEncodingPostProcessor = (encoded) -> { - Matcher m = encoding.matcher(encoded); - while (m.find()) { - String found = m.group(0); - encoded = encoded.replace(found, found.toLowerCase()); - } - return encoded; - }; - } - } - - @Override - public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { - UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - for (Map.Entry entries : request.getParameterMap().entrySet()) { - builder.queryParam(entries.getKey(), - UriUtils.encode(entries.getValue()[0], StandardCharsets.ISO_8859_1)); - } - String queryString = this.urlEncodingPostProcessor.apply(builder.build(true).toUriString().substring(1)); - request.setQueryString(queryString); - return request; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2MetadataConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2MetadataConfigurerTests.java deleted file mode 100644 index 470e99aaea6..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2MetadataConfigurerTests.java +++ /dev/null @@ -1,215 +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.annotation.web.configurers.saml2; - -import com.google.common.net.HttpHeaders; -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -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.saml2.provider.service.metadata.OpenSaml5MetadataResolver; -import org.springframework.security.saml2.provider.service.metadata.RequestMatcherMetadataResponseResolver; -import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponse; -import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponseResolver; -import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.test.web.servlet.MockMvc; - -import static org.hamcrest.Matchers.containsString; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link Saml2MetadataConfigurer} - */ -@ExtendWith(SpringTestContextExtension.class) -public class Saml2MetadataConfigurerTests { - - static RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build(); - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired(required = false) - MockMvc mvc; - - @Test - void saml2MetadataRegistrationIdWhenDefaultsThenReturnsMetadata() throws Exception { - this.spring.register(DefaultConfig.class).autowire(); - String filename = "saml-" + registration.getRegistrationId() + "-metadata.xml"; - this.mvc.perform(get("/saml2/metadata/" + registration.getRegistrationId())) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, containsString(filename))) - .andExpect(content().string(containsString("md:EntityDescriptor"))); - } - - @Test - void saml2MetadataRegistrationIdWhenWrongIdThenUnauthorized() throws Exception { - this.spring.register(DefaultConfig.class).autowire(); - this.mvc.perform(get("/saml2/metadata/" + registration.getRegistrationId() + "wrong")) - .andExpect(status().isUnauthorized()); - } - - @Test - void saml2MetadataWhenDefaultsThenReturnsMetadata() throws Exception { - this.spring.register(DefaultConfig.class).autowire(); - this.mvc.perform(get("/saml2/metadata")) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, containsString("-metadata.xml"))) - .andExpect(content().string(containsString("md:EntityDescriptor"))); - } - - @Test - void saml2MetadataWhenMetadataResponseResolverThenUses() throws Exception { - this.spring.register(DefaultConfig.class, MetadataResponseResolverConfig.class).autowire(); - Saml2MetadataResponseResolver metadataResponseResolver = this.spring.getContext() - .getBean(Saml2MetadataResponseResolver.class); - given(metadataResponseResolver.resolve(any(HttpServletRequest.class))) - .willReturn(new Saml2MetadataResponse("metadata", "filename")); - this.mvc.perform(get("/saml2/metadata")) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, containsString("filename"))) - .andExpect(content().string(containsString("metadata"))); - verify(metadataResponseResolver).resolve(any(HttpServletRequest.class)); - } - - @Test - void saml2MetadataWhenMetadataResponseResolverDslThenUses() throws Exception { - this.spring.register(MetadataResponseResolverDslConfig.class).autowire(); - this.mvc.perform(get("/saml2/metadata")) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, containsString("filename"))) - .andExpect(content().string(containsString("metadata"))); - } - - @Test - void saml2MetadataWhenMetadataUrlThenUses() throws Exception { - this.spring.register(MetadataUrlConfig.class).autowire(); - String filename = "saml-" + registration.getRegistrationId() + "-metadata.xml"; - this.mvc.perform(get("/saml/metadata")) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, containsString(filename))) - .andExpect(content().string(containsString("md:EntityDescriptor"))); - this.mvc.perform(get("/saml2/metadata")).andExpect(status().isForbidden()); - } - - @EnableWebSecurity - @Configuration - @Import(RelyingPartyRegistrationConfig.class) - static class DefaultConfig { - - @Bean - SecurityFilterChain filters(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .saml2Metadata(Customizer.withDefaults()); - return http.build(); - // @formatter:on - } - - } - - @EnableWebSecurity - @Configuration - @Import(RelyingPartyRegistrationConfig.class) - static class MetadataUrlConfig { - - @Bean - SecurityFilterChain filters(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .saml2Metadata((saml2) -> saml2.metadataUrl("/saml/metadata")); - return http.build(); - // @formatter:on - } - - // should ignore - @Bean - Saml2MetadataResponseResolver metadataResponseResolver(RelyingPartyRegistrationRepository registrations) { - return new RequestMatcherMetadataResponseResolver(registrations, new OpenSaml5MetadataResolver()); - } - - } - - @EnableWebSecurity - @Configuration - @Import(RelyingPartyRegistrationConfig.class) - static class MetadataResponseResolverDslConfig { - - Saml2MetadataResponseResolver metadataResponseResolver = mock(Saml2MetadataResponseResolver.class); - - { - given(this.metadataResponseResolver.resolve(any(HttpServletRequest.class))) - .willReturn(new Saml2MetadataResponse("metadata", "filename")); - } - - @Bean - SecurityFilterChain filters(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) - .saml2Metadata((saml2) -> saml2.metadataResponseResolver(this.metadataResponseResolver)); - return http.build(); - // @formatter:on - } - - } - - @Configuration - static class MetadataResponseResolverConfig { - - Saml2MetadataResponseResolver metadataResponseResolver = mock(Saml2MetadataResponseResolver.class); - - @Bean - Saml2MetadataResponseResolver metadataResponseResolver() { - return this.metadataResponseResolver; - } - - } - - @Configuration - static class RelyingPartyRegistrationConfig { - - RelyingPartyRegistrationRepository registrations = new InMemoryRelyingPartyRegistrationRepository(registration); - - @Bean - RelyingPartyRegistrationRepository registrations() { - return this.registrations; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/TestSaml2Credentials.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/TestSaml2Credentials.java deleted file mode 100644 index d0fcfdda70b..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/TestSaml2Credentials.java +++ /dev/null @@ -1,121 +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.annotation.web.configurers.saml2; - -import java.io.ByteArrayInputStream; -import java.nio.charset.StandardCharsets; -import java.security.PrivateKey; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; - -import org.springframework.security.converter.RsaKeyConverters; -import org.springframework.security.saml2.core.Saml2X509Credential; - -/** - * Preconfigured SAML credentials for SAML integration tests. - */ -public final class TestSaml2Credentials { - - private TestSaml2Credentials() { - } - - static Saml2X509Credential verificationCertificate() { - // @formatter:off - String certificate = "-----BEGIN CERTIFICATE-----\n" - + "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD\n" - + "VQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYD\n" - + "VQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwX\n" - + "c2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0Bw\n" - + "aXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJ\n" - + "BgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAa\n" - + "BgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQD\n" - + "DBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlr\n" - + "QHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62\n" - + "E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz\n" - + "2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWW\n" - + "RDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQ\n" - + "nX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5\n" - + "cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gph\n" - + "iJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5\n" - + "ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTAD\n" - + "AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduO\n" - + "nRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+v\n" - + "ZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLu\n" - + "xbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6z\n" - + "V9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3\n" - + "lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" - + "-----END CERTIFICATE-----"; - // @formatter:on - return new Saml2X509Credential(x509Certificate(certificate), - Saml2X509Credential.Saml2X509CredentialType.VERIFICATION); - } - - static X509Certificate x509Certificate(String source) { - try { - final CertificateFactory factory = CertificateFactory.getInstance("X.509"); - return (X509Certificate) factory - .generateCertificate(new ByteArrayInputStream(source.getBytes(StandardCharsets.UTF_8))); - } - catch (Exception ex) { - throw new IllegalArgumentException(ex); - } - } - - static Saml2X509Credential signingCredential() { - // @formatter:off - String key = "-----BEGIN PRIVATE KEY-----\n" - + "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANG7v8QjQGU3MwQE\n" - + "VUBxvH6Uuiy/MhZT7TV0ZNjyAF2ExA1gpn3aUxx6jYK5UnrpxRRE/KbeLucYbOhK\n" - + "cDECt77Rggz5TStrOta0BQTvfluRyoQtmQ5Nkt6Vqg7O2ZapFt7k64Sal7AftzH6\n" - + "Q2BxWN1y04bLdDrH4jipqRj/2qEFAgMBAAECgYEAj4ExY1jjdN3iEDuOwXuRB+Nn\n" - + "x7pC4TgntE2huzdKvLJdGvIouTArce8A6JM5NlTBvm69mMepvAHgcsiMH1zGr5J5\n" - + "wJz23mGOyhM1veON41/DJTVG+cxq4soUZhdYy3bpOuXGMAaJ8QLMbQQoivllNihd\n" - + "vwH0rNSK8LTYWWPZYIECQQDxct+TFX1VsQ1eo41K0T4fu2rWUaxlvjUGhK6HxTmY\n" - + "8OMJptunGRJL1CUjIb45Uz7SP8TPz5FwhXWsLfS182kRAkEA3l+Qd9C9gdpUh1uX\n" - + "oPSNIxn5hFUrSTW1EwP9QH9vhwb5Vr8Jrd5ei678WYDLjUcx648RjkjhU9jSMzIx\n" - + "EGvYtQJBAMm/i9NR7IVyyNIgZUpz5q4LI21rl1r4gUQuD8vA36zM81i4ROeuCly0\n" - + "KkfdxR4PUfnKcQCX11YnHjk9uTFj75ECQEFY/gBnxDjzqyF35hAzrYIiMPQVfznt\n" - + "YX/sDTE2AdVBVGaMj1Cb51bPHnNC6Q5kXKQnj/YrLqRQND09Q7ParX0CQQC5NxZr\n" - + "9jKqhHj8yQD6PlXTsY4Occ7DH6/IoDenfdEVD5qlet0zmd50HatN2Jiqm5ubN7CM\n" - + "INrtuLp4YHbgk1mi\n" - + "-----END PRIVATE KEY-----"; - // @formatter:on - // @formatter:off - String certificate = "-----BEGIN CERTIFICATE-----\n" - + "MIICgTCCAeoCCQCuVzyqFgMSyDANBgkqhkiG9w0BAQsFADCBhDELMAkGA1UEBhMC\n" - + "VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEjAQBgNVBAcMCVZhbmNvdXZlcjEdMBsG\n" - + "A1UECgwUU3ByaW5nIFNlY3VyaXR5IFNBTUwxCzAJBgNVBAsMAnNwMSAwHgYDVQQD\n" - + "DBdzcC5zcHJpbmcuc2VjdXJpdHkuc2FtbDAeFw0xODA1MTQxNDMwNDRaFw0yODA1\n" - + "MTExNDMwNDRaMIGEMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2FzaGluZ3RvbjES\n" - + "MBAGA1UEBwwJVmFuY291dmVyMR0wGwYDVQQKDBRTcHJpbmcgU2VjdXJpdHkgU0FN\n" - + "TDELMAkGA1UECwwCc3AxIDAeBgNVBAMMF3NwLnNwcmluZy5zZWN1cml0eS5zYW1s\n" - + "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRu7/EI0BlNzMEBFVAcbx+lLos\n" - + "vzIWU+01dGTY8gBdhMQNYKZ92lMceo2CuVJ66cUURPym3i7nGGzoSnAxAre+0YIM\n" - + "+U0razrWtAUE735bkcqELZkOTZLelaoOztmWqRbe5OuEmpewH7cx+kNgcVjdctOG\n" - + "y3Q6x+I4qakY/9qhBQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAAeViTvHOyQopWEi\n" - + "XOfI2Z9eukwrSknDwq/zscR0YxwwqDBMt/QdAODfSwAfnciiYLkmEjlozWRtOeN+\n" - + "qK7UFgP1bRl5qksrYX5S0z2iGJh0GvonLUt3e20Ssfl5tTEDDnAEUMLfBkyaxEHD\n" - + "RZ/nbTJ7VTeZOSyRoVn5XHhpuJ0B\n" - + "-----END CERTIFICATE-----"; - // @formatter:on - PrivateKey pk = RsaKeyConverters.pkcs8().convert(new ByteArrayInputStream(key.getBytes())); - X509Certificate cert = x509Certificate(certificate); - return new Saml2X509Credential(pk, cert, Saml2X509Credential.Saml2X509CredentialType.SIGNING, - Saml2X509Credential.Saml2X509CredentialType.DECRYPTION); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurityTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurityTests.java deleted file mode 100644 index 92f89b62148..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurityTests.java +++ /dev/null @@ -1,508 +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.annotation.web.reactive; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.nio.charset.StandardCharsets; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.AliasFor; -import org.springframework.core.annotation.Order; -import org.springframework.core.io.buffer.DataBuffer; -import org.springframework.core.io.buffer.DefaultDataBufferFactory; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockServletContext; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.context.ReactiveSecurityContextHolder; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; -import org.springframework.security.core.userdetails.ReactiveUserDetailsService; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; -import org.springframework.security.web.reactive.result.method.annotation.AuthenticationPrincipalArgumentResolver; -import org.springframework.security.web.reactive.result.view.CsrfRequestDataValueProcessor; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.security.web.server.WebFilterChainProxy; -import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository; -import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.reactive.server.FluxExchangeResult; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import org.springframework.web.reactive.config.DelegatingWebFluxConfiguration; -import org.springframework.web.reactive.config.EnableWebFlux; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.result.view.AbstractView; -import org.springframework.web.server.WebFilter; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf; - -/** - * @author Rob Winch - * @since 5.0 - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class EnableWebFluxSecurityTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - WebFilterChainProxy springSecurityFilterChain; - - @Test - public void defaultRequiresAuthentication() { - this.spring.register(Config.class).autowire(); - // @formatter:off - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(this.springSecurityFilterChain) - .build(); - client.get() - .uri("/") - .exchange() - .expectStatus().isUnauthorized() - .expectBody().isEmpty(); - // @formatter:on - } - - // gh-4831 - @Test - public void defaultMediaAllThenUnAuthorized() { - this.spring.register(Config.class).autowire(); - // @formatter:off - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(this.springSecurityFilterChain) - .build(); - client.get() - .uri("/") - .accept(MediaType.ALL) - .exchange() - .expectStatus().isUnauthorized() - .expectBody().isEmpty(); - // @formatter:on - } - - @Test - public void authenticateWhenBasicThenNoSession() { - this.spring.register(Config.class).autowire(); - // @formatter:off - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(this.springSecurityFilterChain) - .build(); - FluxExchangeResult result = client.get() - .headers((headers) -> headers.setBasicAuth("user", "password")) - .exchange() - .expectStatus().isOk() - .returnResult(String.class); - // @formatter:on - result.assertWithDiagnostics(() -> assertThat(result.getResponseCookies().isEmpty())); - } - - @Test - public void defaultPopulatesReactorContext() { - this.spring.register(Config.class).autowire(); - Authentication currentPrincipal = new TestingAuthenticationToken("user", "password", "ROLE_USER"); - WebSessionServerSecurityContextRepository contextRepository = new WebSessionServerSecurityContextRepository(); - SecurityContext context = new SecurityContextImpl(currentPrincipal); - // @formatter:off - WebFilter contextRepositoryWebFilter = (exchange, chain) -> contextRepository.save(exchange, context) - .switchIfEmpty(chain.filter(exchange)) - .flatMap((e) -> chain.filter(exchange)); - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(contextRepositoryWebFilter, this.springSecurityFilterChain, writePrincipalWebFilter()) - .build(); - client.get() - .uri("/") - .exchange() - .expectStatus().isOk() - .expectBody(String.class).consumeWith((result) -> assertThat(result.getResponseBody()).isEqualTo(currentPrincipal.getName())); - // @formatter:on - } - - private WebFilter writePrincipalWebFilter() { - // @formatter:off - return (exchange, chain) -> ReactiveSecurityContextHolder.getContext() - .map(SecurityContext::getAuthentication) - .flatMap((principal) -> exchange.getResponse() - .writeWith(Mono.just(toDataBuffer(principal.getName()))) - ); - // @formatter:on - } - - @Test - public void defaultPopulatesReactorContextWhenAuthenticating() { - this.spring.register(Config.class).autowire(); - // @formatter:off - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(this.springSecurityFilterChain, writePrincipalWebFilter()) - .build(); - client.get() - .uri("/") - .headers((headers) -> headers.setBasicAuth("user", "password")) - .exchange() - .expectStatus().isOk() - .expectBody(String.class).consumeWith((result) -> assertThat(result.getResponseBody()).isEqualTo("user")); - // @formatter:on - } - - @Test - public void requestDataValueProcessor() { - this.spring.register(Config.class).autowire(); - ConfigurableApplicationContext context = this.spring.getContext(); - CsrfRequestDataValueProcessor rdvp = context.getBean(AbstractView.REQUEST_DATA_VALUE_PROCESSOR_BEAN_NAME, - CsrfRequestDataValueProcessor.class); - assertThat(rdvp).isNotNull(); - } - - @Test - public void passwordEncoderBeanIsUsed() { - this.spring.register(CustomPasswordEncoderConfig.class).autowire(); - // @formatter:off - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(this.springSecurityFilterChain, writePrincipalWebFilter()) - .build(); - client.get().uri("/").headers((headers) -> headers.setBasicAuth("user", "password")) - .exchange().expectStatus().isOk() - .expectBody(String.class) - .consumeWith((result) -> assertThat(result.getResponseBody()).isEqualTo("user")); - // @formatter:on - } - - @Test - public void passwordUpdateManagerUsed() { - this.spring.register(MapReactiveUserDetailsServiceConfig.class).autowire(); - // @formatter:off - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(this.springSecurityFilterChain) - .build(); - client.get() - .uri("/") - .headers((h) -> h.setBasicAuth("user", "password")) - .exchange() - .expectStatus().isOk(); - // @formatter:on - ReactiveUserDetailsService users = this.spring.getContext().getBean(ReactiveUserDetailsService.class); - assertThat(users.findByUsername("user").block().getPassword()).startsWith("{bcrypt}"); - } - - @Test - public void formLoginWorks() { - this.spring.register(Config.class).autowire(); - // @formatter:off - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(this.springSecurityFilterChain, writePrincipalWebFilter()) - .build(); - // @formatter:on - MultiValueMap data = new LinkedMultiValueMap<>(); - data.add("username", "user"); - data.add("password", "password"); - // @formatter:off - client.mutateWith(csrf()) - .post() - .uri("/login") - .body(BodyInserters.fromFormData(data)) - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().valueMatches("Location", "/"); - // @formatter:on - } - - @Test - public void multiWorks() { - this.spring.register(MultiSecurityHttpConfig.class).autowire(); - // @formatter:off - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(this.springSecurityFilterChain) - .build(); - client.get() - .uri("/api/test") - .exchange() - .expectStatus().isUnauthorized() - .expectBody().isEmpty(); - client.get() - .uri("/test") - .exchange() - .expectStatus().isOk(); - // @formatter:on - } - - @Test - @WithMockUser - public void authenticationPrincipalArgumentResolverWhenSpelThenWorks() { - this.spring.register(AuthenticationPrincipalConfig.class).autowire(); - // @formatter:off - WebTestClient client = WebTestClient - .bindToApplicationContext(this.spring.getContext()) - .build(); - client.get() - .uri("/spel") - .exchange() - .expectStatus().isOk() - .expectBody(String.class).isEqualTo("user"); - // @formatter:on - } - - private static DataBuffer toDataBuffer(String body) { - DataBuffer buffer = new DefaultDataBufferFactory().allocateBuffer(); - buffer.write(body.getBytes(StandardCharsets.UTF_8)); - return buffer; - } - - @Test - public void enableWebFluxSecurityWhenNoConfigurationAnnotationThenBeanProxyingEnabled() { - this.spring.register(BeanProxyEnabledByDefaultConfig.class).autowire(); - Child childBean = this.spring.getContext().getBean(Child.class); - Parent parentBean = this.spring.getContext().getBean(Parent.class); - assertThat(parentBean.getChild()).isSameAs(childBean); - } - - @Test - public void enableWebFluxSecurityWhenProxyBeanMethodsFalseThenBeanProxyingDisabled() { - this.spring.register(BeanProxyDisabledConfig.class).autowire(); - Child childBean = this.spring.getContext().getBean(Child.class); - Parent parentBean = this.spring.getContext().getBean(Parent.class); - assertThat(parentBean.getChild()).isNotSameAs(childBean); - } - - @Test - // gh-8596 - public void resolveAuthenticationPrincipalArgumentResolverFirstDoesNotCauseBeanCurrentlyInCreationException() { - this.spring - .register(EnableWebFluxSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, - DelegatingWebFluxConfiguration.class) - .autowire(); - } - - @Test - // gh-10076 - public void webFluxConfigurationSupportAndServerHttpSecurityConfigurationDoNotCauseCircularReference() { - AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); - context.setAllowCircularReferences(false); - context.register(EnableWebFluxSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, - DelegatingWebFluxConfiguration.class); - context.setServletContext(new MockServletContext()); - context.refresh(); - } - - @Configuration - @EnableWebFluxSecurity - @Import(ReactiveAuthenticationTestConfiguration.class) - static class Config { - - } - - @Configuration - @EnableWebFluxSecurity - static class CustomPasswordEncoderConfig { - - @Bean - ReactiveUserDetailsService userDetailsService(PasswordEncoder encoder) { - return new MapReactiveUserDetailsService( - User.withUsername("user").password(encoder.encode("password")).roles("USER").build()); - } - - @Bean - static PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - - } - - @Configuration - @EnableWebFluxSecurity - static class MapReactiveUserDetailsServiceConfig { - - @Bean - MapReactiveUserDetailsService userDetailsService() { - // @formatter:off - return new MapReactiveUserDetailsService(User.withUsername("user") - .password("{noop}password") - .roles("USER") - .build() - // @formatter:on - ); - } - - } - - @Configuration - @EnableWebFluxSecurity - @Import(ReactiveAuthenticationTestConfiguration.class) - static class MultiSecurityHttpConfig { - - @Order(Ordered.HIGHEST_PRECEDENCE) - @Bean - SecurityWebFilterChain apiHttpSecurity(ServerHttpSecurity http) { - http.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/api/**")) - .authorizeExchange((authorize) -> authorize.anyExchange().denyAll()); - return http.build(); - } - - @Bean - SecurityWebFilterChain httpSecurity(ServerHttpSecurity http) { - return http.build(); - } - - } - - @Configuration - @EnableWebFluxSecurity - @EnableWebFlux - @Import(ReactiveAuthenticationTestConfiguration.class) - static class AuthenticationPrincipalConfig { - - @Bean - PrincipalBean principalBean() { - return new PrincipalBean(); - } - - static class PrincipalBean { - - public String username(UserDetails user) { - return user.getUsername(); - } - - } - - @Target({ ElementType.PARAMETER }) - @Retention(RetentionPolicy.RUNTIME) - @AuthenticationPrincipal - @interface Property { - - @AliasFor(attribute = "expression", annotation = AuthenticationPrincipal.class) - String value() default "id"; - - } - - interface UsernameResolver { - - String username(@Property("@principalBean.username(#this)") String username); - - } - - @RestController - static class AuthenticationPrincipalResolver implements UsernameResolver { - - @Override - @GetMapping("/spel") - public String username(String username) { - return username; - } - - } - - } - - @Configuration - @EnableWebFluxSecurity - @Import(ReactiveAuthenticationTestConfiguration.class) - static class BeanProxyEnabledByDefaultConfig { - - @Bean - Child child() { - return new Child(); - } - - @Bean - Parent parent() { - return new Parent(child()); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableWebFluxSecurity - @Import(ReactiveAuthenticationTestConfiguration.class) - static class BeanProxyDisabledConfig { - - @Bean - Child child() { - return new Child(); - } - - @Bean - Parent parent() { - return new Parent(child()); - } - - } - - static class Parent { - - private Child child; - - Parent(Child child) { - this.child = child; - } - - Child getChild() { - return this.child; - } - - } - - static class Child { - - Child() { - } - - } - - @EnableWebFluxSecurity - @Configuration(proxyBeanMethods = false) - static class EnableWebFluxSecurityConfiguration { - - /** - * It is necessary to Autowire AuthenticationPrincipalArgumentResolver because it - * triggers eager loading of AuthenticationPrincipalArgumentResolver bean which - * causes BeanCurrentlyInCreationException - */ - @Autowired - AuthenticationPrincipalArgumentResolver resolver; - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2AuthorizedClientManagerConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2AuthorizedClientManagerConfigurationTests.java deleted file mode 100644 index f25a566785d..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2AuthorizedClientManagerConfigurationTests.java +++ /dev/null @@ -1,492 +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.annotation.web.reactive; - -import java.time.Duration; -import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.oauth2.client.AuthorizationCodeReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException; -import org.springframework.security.oauth2.client.ClientCredentialsReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.JwtBearerReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.OAuth2AuthorizationContext; -import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.RefreshTokenReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.TokenExchangeReactiveOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest; -import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest; -import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; -import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; -import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest; -import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.client.web.server.WebSessionServerOAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthorizationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; -import org.springframework.security.oauth2.jwt.JoseHeaderNames; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimNames; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -/** - * Tests for - * {@link ReactiveOAuth2ClientConfiguration.ReactiveOAuth2AuthorizedClientManagerConfiguration}. - * - * @author Steve Riesenberg - */ -public class ReactiveOAuth2AuthorizedClientManagerConfigurationTests { - - private static ReactiveOAuth2AccessTokenResponseClient MOCK_RESPONSE_CLIENT; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private ReactiveOAuth2AuthorizedClientManager authorizedClientManager; - - @Autowired - private ReactiveClientRegistrationRepository clientRegistrationRepository; - - @Autowired(required = false) - private ServerOAuth2AuthorizedClientRepository authorizedClientRepository; - - @Autowired(required = false) - private ReactiveOAuth2AuthorizedClientService authorizedClientService; - - @Autowired(required = false) - private AuthorizationCodeReactiveOAuth2AuthorizedClientProvider authorizationCodeAuthorizedClientProvider; - - private MockServerWebExchange exchange; - - @BeforeEach - @SuppressWarnings("unchecked") - public void setUp() { - MOCK_RESPONSE_CLIENT = mock(ReactiveOAuth2AccessTokenResponseClient.class); - MockServerHttpRequest request = MockServerHttpRequest.get("/").build(); - this.exchange = MockServerWebExchange.builder(request).build(); - } - - @Test - public void loadContextWhenOAuth2ClientEnabledThenConfigured() { - this.spring.register(MinimalOAuth2ClientConfig.class).autowire(); - assertThat(this.authorizedClientManager).isNotNull(); - } - - @Test - public void authorizeWhenAuthorizationCodeAuthorizedClientProviderBeanThenUsed() { - this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); - - TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null, "ROLE_USER"); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId("google") - .principal(authentication) - .attribute(ServerWebExchange.class.getName(), this.exchange) - .build(); - assertThatExceptionOfType(ClientAuthorizationRequiredException.class) - .isThrownBy(() -> this.authorizedClientManager.authorize(authorizeRequest).block()) - .extracting(OAuth2AuthorizationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo("client_authorization_required"); - // @formatter:on - - verify(this.authorizationCodeAuthorizedClientProvider).authorize(any(OAuth2AuthorizationContext.class)); - } - - @Test - public void authorizeWhenAuthorizedClientServiceBeanThenUsed() { - this.spring.register(CustomAuthorizedClientServiceConfig.class).autowire(); - - TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null, "ROLE_USER"); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId("google") - .principal(authentication) - .attribute(ServerWebExchange.class.getName(), this.exchange) - .build(); - assertThatExceptionOfType(ClientAuthorizationRequiredException.class) - .isThrownBy(() -> this.authorizedClientManager.authorize(authorizeRequest).block()) - .extracting(OAuth2AuthorizationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo("client_authorization_required"); - // @formatter:on - - verify(this.authorizedClientService).loadAuthorizedClient(authorizeRequest.getClientRegistrationId(), - authentication.getName()); - } - - @Test - public void authorizeWhenRefreshTokenAccessTokenResponseClientBeanThenUsed() { - this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire(); - testRefreshTokenGrant(); - } - - @Test - public void authorizeWhenRefreshTokenAuthorizedClientProviderBeanThenUsed() { - this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); - testRefreshTokenGrant(); - } - - private void testRefreshTokenGrant() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) - .willReturn(Mono.just(accessTokenResponse)); - - TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null, "ROLE_USER"); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google") - .block(); - assertThat(clientRegistration).isNotNull(); - OAuth2AuthorizedClient existingAuthorizedClient = new OAuth2AuthorizedClient(clientRegistration, - authentication.getName(), getExpiredAccessToken(), TestOAuth2RefreshTokens.refreshToken()); - this.authorizedClientRepository.saveAuthorizedClient(existingAuthorizedClient, authentication, this.exchange) - .block(); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(clientRegistration.getRegistrationId()) - .principal(authentication) - .attribute(ServerWebExchange.class.getName(), this.exchange) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block(); - assertThat(authorizedClient).isNotNull(); - - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(OAuth2RefreshTokenGrantRequest.class); - verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); - - OAuth2RefreshTokenGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getClientRegistration().getRegistrationId()) - .isEqualTo(clientRegistration.getRegistrationId()); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN); - assertThat(grantRequest.getAccessToken()).isEqualTo(existingAuthorizedClient.getAccessToken()); - assertThat(grantRequest.getRefreshToken()).isEqualTo(existingAuthorizedClient.getRefreshToken()); - } - - @Test - public void authorizeWhenClientCredentialsAccessTokenResponseClientBeanThenUsed() { - this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire(); - testClientCredentialsGrant(); - } - - @Test - public void authorizeWhenClientCredentialsAuthorizedClientProviderBeanThenUsed() { - this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); - testClientCredentialsGrant(); - } - - private void testClientCredentialsGrant() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2ClientCredentialsGrantRequest.class))) - .willReturn(Mono.just(accessTokenResponse)); - - TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null, "ROLE_USER"); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github") - .block(); - assertThat(clientRegistration).isNotNull(); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(clientRegistration.getRegistrationId()) - .principal(authentication) - .attribute(ServerWebExchange.class.getName(), this.exchange) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block(); - assertThat(authorizedClient).isNotNull(); - - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(OAuth2ClientCredentialsGrantRequest.class); - verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); - - OAuth2ClientCredentialsGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getClientRegistration().getRegistrationId()) - .isEqualTo(clientRegistration.getRegistrationId()); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS); - } - - @Test - public void authorizeWhenJwtBearerAccessTokenResponseClientBeanThenUsed() { - this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire(); - testJwtBearerGrant(); - } - - @Test - public void authorizeWhenJwtBearerAuthorizedClientProviderBeanThenUsed() { - this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); - testJwtBearerGrant(); - } - - private void testJwtBearerGrant() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(JwtBearerGrantRequest.class))) - .willReturn(Mono.just(accessTokenResponse)); - - JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt()); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("okta").block(); - assertThat(clientRegistration).isNotNull(); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(clientRegistration.getRegistrationId()) - .principal(authentication) - .attribute(ServerWebExchange.class.getName(), this.exchange) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block(); - assertThat(authorizedClient).isNotNull(); - - ArgumentCaptor grantRequestCaptor = ArgumentCaptor.forClass(JwtBearerGrantRequest.class); - verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); - - JwtBearerGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getClientRegistration().getRegistrationId()) - .isEqualTo(clientRegistration.getRegistrationId()); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.JWT_BEARER); - assertThat(grantRequest.getJwt().getSubject()).isEqualTo("user"); - } - - @Test - public void authorizeWhenTokenExchangeAccessTokenResponseClientBeanThenUsed() { - this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire(); - testTokenExchangeGrant(); - } - - @Test - public void authorizeWhenTokenExchangeAuthorizedClientProviderBeanThenUsed() { - this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire(); - testTokenExchangeGrant(); - } - - private void testTokenExchangeGrant() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(TokenExchangeGrantRequest.class))) - .willReturn(Mono.just(accessTokenResponse)); - - JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt()); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("auth0").block(); - assertThat(clientRegistration).isNotNull(); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(clientRegistration.getRegistrationId()) - .principal(authentication) - .attribute(ServerWebExchange.class.getName(), this.exchange) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest).block(); - assertThat(authorizedClient).isNotNull(); - - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); - - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getClientRegistration().getRegistrationId()) - .isEqualTo(clientRegistration.getRegistrationId()); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); - assertThat(grantRequest.getSubjectToken()).isEqualTo(authentication.getToken()); - } - - private static OAuth2AccessToken getExpiredAccessToken() { - Instant expiresAt = Instant.now().minusSeconds(60); - Instant issuedAt = expiresAt.minus(Duration.ofDays(1)); - return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "scopes", issuedAt, expiresAt, - new HashSet<>(Arrays.asList("read", "write"))); - } - - private static Jwt getJwt() { - Instant issuedAt = Instant.now(); - return new Jwt("token", issuedAt, issuedAt.plusSeconds(300), - Collections.singletonMap(JoseHeaderNames.ALG, "RS256"), - Collections.singletonMap(JwtClaimNames.SUB, "user")); - } - - @Configuration - @EnableWebFluxSecurity - static class MinimalOAuth2ClientConfig extends OAuth2ClientBaseConfig { - - @Bean - ServerOAuth2AuthorizedClientRepository authorizedClientRepository() { - return new WebSessionServerOAuth2AuthorizedClientRepository(); - } - - } - - @Configuration - @EnableWebFluxSecurity - static class CustomAuthorizedClientServiceConfig extends OAuth2ClientBaseConfig { - - @Bean - ReactiveOAuth2AuthorizedClientService authorizedClientService() { - ReactiveOAuth2AuthorizedClientService authorizedClientService = mock( - ReactiveOAuth2AuthorizedClientService.class); - given(authorizedClientService.loadAuthorizedClient(anyString(), anyString())).willReturn(Mono.empty()); - return authorizedClientService; - } - - } - - @Configuration - @EnableWebFluxSecurity - static class CustomAccessTokenResponseClientsConfig extends MinimalOAuth2ClientConfig { - - @Bean - ReactiveOAuth2AccessTokenResponseClient authorizationCodeAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - - @Bean - ReactiveOAuth2AccessTokenResponseClient refreshTokenTokenAccessResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - - @Bean - ReactiveOAuth2AccessTokenResponseClient clientCredentialsAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - - @Bean - ReactiveOAuth2AccessTokenResponseClient jwtBearerAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - - @Bean - ReactiveOAuth2AccessTokenResponseClient tokenExchangeAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - - } - - @Configuration - @EnableWebFluxSecurity - static class CustomAuthorizedClientProvidersConfig extends MinimalOAuth2ClientConfig { - - @Bean - AuthorizationCodeReactiveOAuth2AuthorizedClientProvider authorizationCode() { - return spy(new AuthorizationCodeReactiveOAuth2AuthorizedClientProvider()); - } - - @Bean - RefreshTokenReactiveOAuth2AuthorizedClientProvider refreshToken() { - RefreshTokenReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenReactiveOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); - return authorizedClientProvider; - } - - @Bean - ClientCredentialsReactiveOAuth2AuthorizedClientProvider clientCredentials() { - ClientCredentialsReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsReactiveOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); - return authorizedClientProvider; - } - - @Bean - JwtBearerReactiveOAuth2AuthorizedClientProvider jwtBearer() { - JwtBearerReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerReactiveOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); - return authorizedClientProvider; - } - - @Bean - TokenExchangeReactiveOAuth2AuthorizedClientProvider tokenExchange() { - TokenExchangeReactiveOAuth2AuthorizedClientProvider authorizedClientProvider = new TokenExchangeReactiveOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(new MockAccessTokenResponseClient<>()); - return authorizedClientProvider; - } - - } - - abstract static class OAuth2ClientBaseConfig { - - @Bean - ReactiveClientRegistrationRepository clientRegistrationRepository() { - // @formatter:off - return new InMemoryReactiveClientRegistrationRepository( - CommonOAuth2Provider.GOOGLE.getBuilder("google") - .clientId("google-client-id") - .clientSecret("google-client-secret") - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .build(), - CommonOAuth2Provider.GITHUB.getBuilder("github") - .clientId("github-client-id") - .clientSecret("github-client-secret") - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .build(), - CommonOAuth2Provider.OKTA.getBuilder("okta") - .clientId("okta-client-id") - .clientSecret("okta-client-secret") - .authorizationGrantType(AuthorizationGrantType.JWT_BEARER) - .build(), - ClientRegistration.withRegistrationId("auth0") - .clientName("Auth0") - .clientId("auth0-client-id") - .clientSecret("auth0-client-secret") - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .scope("user.read", "user.write") - .build()); - // @formatter:on - } - - } - - private static class MockAccessTokenResponseClient - implements ReactiveOAuth2AccessTokenResponseClient { - - @Override - public Mono getTokenResponse(T grantRequest) { - return MOCK_RESPONSE_CLIENT.getTokenResponse(grantRequest); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelectorTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelectorTests.java deleted file mode 100644 index c0b65313b2f..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ReactiveOAuth2ClientImportSelectorTests.java +++ /dev/null @@ -1,173 +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.annotation.web.reactive; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.config.EnableWebFlux; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -/** - * Tests for {@link ReactiveOAuth2ClientImportSelector}. - * - * @author Alavudin Kuttikkattil - */ -@ExtendWith(SpringTestContextExtension.class) -public class ReactiveOAuth2ClientImportSelectorTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - WebTestClient client; - - @Autowired - public void setApplicationContext(ApplicationContext context) { - // @formatter:off - this.client = WebTestClient - .bindToApplicationContext(context) - .build(); - // @formatter:on - } - - @Test - public void requestWhenAuthorizedClientManagerConfiguredThenUsed() { - String clientRegistrationId = "client"; - String principalName = "user"; - ReactiveClientRegistrationRepository clientRegistrationRepository = mock( - ReactiveClientRegistrationRepository.class); - ServerOAuth2AuthorizedClientRepository authorizedClientRepository = mock( - ServerOAuth2AuthorizedClientRepository.class); - ReactiveOAuth2AuthorizedClientManager authorizedClientManager = mock( - ReactiveOAuth2AuthorizedClientManager.class); - ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials() - .registrationId(clientRegistrationId) - .build(); - OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principalName, - TestOAuth2AccessTokens.noScopes()); - given(authorizedClientManager.authorize(any())).willReturn(Mono.just(authorizedClient)); - OAuth2AuthorizedClientManagerRegisteredConfig.CLIENT_REGISTRATION_REPOSITORY = clientRegistrationRepository; - OAuth2AuthorizedClientManagerRegisteredConfig.AUTHORIZED_CLIENT_REPOSITORY = authorizedClientRepository; - OAuth2AuthorizedClientManagerRegisteredConfig.AUTHORIZED_CLIENT_MANAGER = authorizedClientManager; - this.spring.register(OAuth2AuthorizedClientManagerRegisteredConfig.class).autowire(); - // @formatter:off - this.client - .get() - .uri("http://localhost/authorized-client") - .headers((headers) -> headers.setBasicAuth("user", "password")).exchange().expectStatus().isOk() - .expectBody(String.class).isEqualTo("resolved"); - // @formatter:on - verify(authorizedClientManager).authorize(any()); - verifyNoInteractions(clientRegistrationRepository); - verifyNoInteractions(authorizedClientRepository); - } - - @Test - public void requestWhenAuthorizedClientManagerNotConfigureThenUseDefaultAuthorizedClientManager() { - String clientRegistrationId = "client"; - String principalName = "user"; - ReactiveClientRegistrationRepository clientRegistrationRepository = mock( - ReactiveClientRegistrationRepository.class); - ServerOAuth2AuthorizedClientRepository authorizedClientRepository = mock( - ServerOAuth2AuthorizedClientRepository.class); - ClientRegistration clientRegistration = TestClientRegistrations.clientCredentials() - .registrationId(clientRegistrationId) - .build(); - OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principalName, - TestOAuth2AccessTokens.noScopes()); - OAuth2AuthorizedClientManagerRegisteredConfig.CLIENT_REGISTRATION_REPOSITORY = clientRegistrationRepository; - OAuth2AuthorizedClientManagerRegisteredConfig.AUTHORIZED_CLIENT_REPOSITORY = authorizedClientRepository; - OAuth2AuthorizedClientManagerRegisteredConfig.AUTHORIZED_CLIENT_MANAGER = null; - given(authorizedClientRepository.loadAuthorizedClient(any(), any(), any())) - .willReturn(Mono.just(authorizedClient)); - this.spring.register(OAuth2AuthorizedClientManagerRegisteredConfig.class).autowire(); - // @formatter:off - this.client - .get() - .uri("http://localhost/authorized-client") - .headers((headers) -> headers.setBasicAuth("user", "password")).exchange().expectStatus().isOk() - .expectBody(String.class).isEqualTo("resolved"); - // @formatter:on - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class OAuth2AuthorizedClientManagerRegisteredConfig { - - static ReactiveClientRegistrationRepository CLIENT_REGISTRATION_REPOSITORY; - static ServerOAuth2AuthorizedClientRepository AUTHORIZED_CLIENT_REPOSITORY; - static ReactiveOAuth2AuthorizedClientManager AUTHORIZED_CLIENT_MANAGER; - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - return http.build(); - } - - @Bean - ReactiveClientRegistrationRepository clientRegistrationRepository() { - return CLIENT_REGISTRATION_REPOSITORY; - } - - @Bean - ServerOAuth2AuthorizedClientRepository authorizedClientRepository() { - return AUTHORIZED_CLIENT_REPOSITORY; - } - - @Bean - ReactiveOAuth2AuthorizedClientManager authorizedClientManager() { - return AUTHORIZED_CLIENT_MANAGER; - } - - @RestController - class Controller { - - @GetMapping("/authorized-client") - String authorizedClient( - @RegisteredOAuth2AuthorizedClient("client1") OAuth2AuthorizedClient authorizedClient) { - return (authorizedClient != null) ? "resolved" : "not-resolved"; - } - - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfigurationBuilder.java b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfigurationBuilder.java deleted file mode 100644 index 4a8493c4a48..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfigurationBuilder.java +++ /dev/null @@ -1,46 +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.annotation.web.reactive; - -import org.springframework.security.authentication.ReactiveAuthenticationManager; -import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; -import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.core.userdetails.ReactiveUserDetailsService; - -/** - * @author Rob Winch - * @since 5.0 - */ -public final class ServerHttpSecurityConfigurationBuilder { - - private ServerHttpSecurityConfigurationBuilder() { - } - - public static ServerHttpSecurity http() { - return new ServerHttpSecurityConfiguration().httpSecurity(); - } - - public static ServerHttpSecurity httpWithDefaultAuthentication() { - ReactiveUserDetailsService reactiveUserDetailsService = ReactiveAuthenticationTestConfiguration - .userDetailsService(); - ReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager( - reactiveUserDetailsService); - return http().authenticationManager(authenticationManager); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfigurationTests.java deleted file mode 100644 index b68736ced46..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/ServerHttpSecurityConfigurationTests.java +++ /dev/null @@ -1,584 +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.annotation.web.reactive; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.net.URI; -import java.util.Iterator; - -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationHandler; -import io.micrometer.observation.ObservationRegistry; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.InOrder; -import org.mockito.Mockito; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.core.Ordered; -import org.springframework.core.annotation.Order; -import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authentication.password.CompromisedPasswordDecision; -import org.springframework.security.authentication.password.CompromisedPasswordException; -import org.springframework.security.authentication.password.ReactiveCompromisedPasswordChecker; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.rsocket.EnableRSocketSecurity; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration; -import org.springframework.security.config.web.server.ServerHttpSecurity; -import org.springframework.security.config.web.server.ServerHttpSecurity.AuthorizeExchangeSpec; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.annotation.CurrentSecurityContext; -import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; -import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.ReactiveUserDetailsService; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.web.server.DefaultServerRedirectStrategy; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.config.EnableWebFlux; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.server.adapter.WebHttpHandlerBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockAuthentication; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; - -/** - * Tests for {@link ServerHttpSecurityConfiguration}. - * - * @author Eleftheria Stein - */ -@ExtendWith(SpringTestContextExtension.class) -public class ServerHttpSecurityConfigurationTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - WebTestClient webClient; - - @Autowired - void setup(ApplicationContext context) { - if (!context.containsBean(WebHttpHandlerBuilder.WEB_HANDLER_BEAN_NAME)) { - return; - } - this.webClient = WebTestClient.bindToApplicationContext(context) - .apply(springSecurity()) - .configureClient() - .build(); - } - - @Test - public void loadConfigWhenReactiveUserDetailsServiceConfiguredThenServerHttpSecurityExists() { - this.spring - .register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, - WebFluxSecurityConfiguration.class) - .autowire(); - ServerHttpSecurity serverHttpSecurity = this.spring.getContext().getBean(ServerHttpSecurity.class); - assertThat(serverHttpSecurity).isNotNull(); - } - - @Test - public void loadConfigWhenProxyingEnabledAndSubclassThenServerHttpSecurityExists() { - this.spring - .register(SubclassConfig.class, ReactiveAuthenticationTestConfiguration.class, - WebFluxSecurityConfiguration.class) - .autowire(); - ServerHttpSecurity serverHttpSecurity = this.spring.getContext().getBean(ServerHttpSecurity.class); - assertThat(serverHttpSecurity).isNotNull(); - } - - @Test - void loginWhenCompromisePasswordCheckerConfiguredAndPasswordCompromisedThenUnauthorized() { - this.spring.register(FormLoginConfig.class, UserDetailsConfig.class, CompromisedPasswordCheckerConfig.class) - .autowire(); - MultiValueMap data = new LinkedMultiValueMap<>(); - data.add("username", "user"); - data.add("password", "password"); - // @formatter:off - this.webClient.mutateWith(csrf()) - .post() - .uri("/login") - .body(BodyInserters.fromFormData(data)) - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().location("/login?error"); - // @formatter:on - } - - @Test - void loginWhenCompromisePasswordCheckerConfiguredAndPasswordNotCompromisedThenUnauthorized() { - this.spring.register(FormLoginConfig.class, UserDetailsConfig.class, CompromisedPasswordCheckerConfig.class) - .autowire(); - MultiValueMap data = new LinkedMultiValueMap<>(); - data.add("username", "admin"); - data.add("password", "password2"); - // @formatter:off - this.webClient.mutateWith(csrf()) - .post() - .uri("/login") - .body(BodyInserters.fromFormData(data)) - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().location("/"); - // @formatter:on - } - - @Test - void loginWhenCompromisedPasswordAndRedirectIfPasswordExceptionThenRedirectedToResetPassword() { - this.spring - .register(FormLoginRedirectToResetPasswordConfig.class, UserDetailsConfig.class, - CompromisedPasswordCheckerConfig.class) - .autowire(); - MultiValueMap data = new LinkedMultiValueMap<>(); - data.add("username", "user"); - data.add("password", "password"); - // @formatter:off - this.webClient.mutateWith(csrf()) - .post() - .uri("/login") - .body(BodyInserters.fromFormData(data)) - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().location("/reset-password"); - // @formatter:on - } - - @Test - public void metaAnnotationWhenTemplateDefaultsBeanThenResolvesExpression() throws Exception { - this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); - Authentication user = new TestingAuthenticationToken("user", "password", "ROLE_USER"); - this.webClient.mutateWith(mockAuthentication(user)) - .get() - .uri("/hi") - .exchange() - .expectStatus() - .isOk() - .expectBody(String.class) - .isEqualTo("Hi, Stranger!"); - Authentication harold = new TestingAuthenticationToken("harold", "password", "ROLE_USER"); - this.webClient.mutateWith(mockAuthentication(harold)) - .get() - .uri("/hi") - .exchange() - .expectBody(String.class) - .isEqualTo("Hi, Harold!"); - } - - @Test - public void resoleMetaAnnotationWhenTemplateDefaultsBeanThenResolvesExpression() throws Exception { - this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); - Authentication user = new TestingAuthenticationToken("user", "password", "ROLE_USER"); - this.webClient.mutateWith(mockAuthentication(user)) - .get() - .uri("/hello") - .exchange() - .expectStatus() - .isOk() - .expectBody(String.class) - .isEqualTo("user"); - Authentication harold = new TestingAuthenticationToken("harold", "password", "ROLE_USER"); - this.webClient.mutateWith(mockAuthentication(harold)) - .get() - .uri("/hello") - .exchange() - .expectBody(String.class) - .isEqualTo("harold"); - } - - @Test - public void getWhenUsingObservationRegistryThenObservesRequest() { - this.spring.register(ObservationRegistryConfig.class).autowire(); - // @formatter:off - this.webClient - .get() - .uri("/hello") - .headers((headers) -> headers.setBasicAuth("user", "password")) - .exchange() - .expectStatus() - .isNotFound(); - // @formatter:on - ObservationHandler handler = this.spring.getContext().getBean(ObservationHandler.class); - ArgumentCaptor captor = ArgumentCaptor.forClass(Observation.Context.class); - verify(handler, times(6)).onStart(captor.capture()); - Iterator contexts = captor.getAllValues().iterator(); - assertThat(contexts.next().getContextualName()).isEqualTo("http get"); - assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain before"); - assertThat(contexts.next().getName()).isEqualTo("spring.security.authentications"); - assertThat(contexts.next().getName()).isEqualTo("spring.security.authorizations"); - assertThat(contexts.next().getName()).isEqualTo("spring.security.http.secured.requests"); - assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain after"); - } - - // gh-16161 - @Test - public void getWhenUsingRSocketThenObservesRequest() { - this.spring.register(ObservationRegistryConfig.class, RSocketSecurityConfig.class).autowire(); - // @formatter:off - this.webClient - .get() - .uri("/hello") - .headers((headers) -> headers.setBasicAuth("user", "password")) - .exchange() - .expectStatus() - .isNotFound(); - // @formatter:on - ObservationHandler handler = this.spring.getContext().getBean(ObservationHandler.class); - ArgumentCaptor captor = ArgumentCaptor.forClass(Observation.Context.class); - verify(handler, times(6)).onStart(captor.capture()); - Iterator contexts = captor.getAllValues().iterator(); - assertThat(contexts.next().getContextualName()).isEqualTo("http get"); - assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain before"); - assertThat(contexts.next().getName()).isEqualTo("spring.security.authentications"); - assertThat(contexts.next().getName()).isEqualTo("spring.security.authorizations"); - assertThat(contexts.next().getName()).isEqualTo("spring.security.http.secured.requests"); - assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain after"); - } - - @Test - void authorizeExchangeCustomizerBean() { - this.spring.register(AuthorizeExchangeCustomizerBeanConfig.class).autowire(); - Customizer authzCustomizer = this.spring.getContext().getBean("authz", Customizer.class); - - ArgumentCaptor arg0 = ArgumentCaptor.forClass(AuthorizeExchangeSpec.class); - verify(authzCustomizer).customize(arg0.capture()); - } - - @Test - void multiAuthorizeExchangeCustomizerBean() { - this.spring.register(MultiAuthorizeExchangeCustomizerBeanConfig.class).autowire(); - Customizer authzCustomizer = this.spring.getContext().getBean("authz", Customizer.class); - - ArgumentCaptor arg0 = ArgumentCaptor.forClass(AuthorizeExchangeSpec.class); - verify(authzCustomizer).customize(arg0.capture()); - } - - @Test - void serverHttpSecurityCustomizerBean() { - this.spring.register(ServerHttpSecurityCustomizerConfig.class).autowire(); - Customizer httpSecurityCustomizer = this.spring.getContext() - .getBean("httpSecurityCustomizer", Customizer.class); - - ArgumentCaptor arg0 = ArgumentCaptor.forClass(ServerHttpSecurity.class); - verify(httpSecurityCustomizer).customize(arg0.capture()); - } - - @Test - void multiServerHttpSecurityCustomizerBean() { - this.spring.register(MultiServerHttpSecurityCustomizerConfig.class).autowire(); - Customizer httpSecurityCustomizer = this.spring.getContext() - .getBean("httpSecurityCustomizer", Customizer.class); - Customizer httpSecurityCustomizer0 = this.spring.getContext() - .getBean("httpSecurityCustomizer0", Customizer.class); - InOrder inOrder = Mockito.inOrder(httpSecurityCustomizer0, httpSecurityCustomizer); - ArgumentCaptor arg0 = ArgumentCaptor.forClass(ServerHttpSecurity.class); - inOrder.verify(httpSecurityCustomizer0).customize(arg0.capture()); - inOrder.verify(httpSecurityCustomizer).customize(arg0.capture()); - } - - @Configuration - static class SubclassConfig extends ServerHttpSecurityConfiguration { - - } - - @Configuration(proxyBeanMethods = false) - @EnableWebFlux - @EnableWebFluxSecurity - static class FormLoginConfig { - - @Bean - SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated() - ) - .formLogin(Customizer.withDefaults()); - // @formatter:on - return http.build(); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableWebFlux - @EnableWebFluxSecurity - static class FormLoginRedirectToResetPasswordConfig { - - @Bean - SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated() - ) - .formLogin((form) -> form - .authenticationFailureHandler((webFilterExchange, exception) -> { - String redirectUrl = "/login?error"; - if (exception instanceof CompromisedPasswordException) { - redirectUrl = "/reset-password"; - } - return new DefaultServerRedirectStrategy().sendRedirect(webFilterExchange.getExchange(), URI.create(redirectUrl)); - }) - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration(proxyBeanMethods = false) - static class UserDetailsConfig { - - @Bean - MapReactiveUserDetailsService userDetailsService() { - // @formatter:off - UserDetails user = PasswordEncodedUser.user(); - UserDetails admin = User.withDefaultPasswordEncoder() - .username("admin") - .password("password2") - .roles("USER", "ADMIN") - .build(); - // @formatter:on - return new MapReactiveUserDetailsService(user, admin); - } - - } - - @Configuration(proxyBeanMethods = false) - static class CompromisedPasswordCheckerConfig { - - @Bean - TestReactivePasswordChecker compromisedPasswordChecker() { - return new TestReactivePasswordChecker(); - } - - } - - static class TestReactivePasswordChecker implements ReactiveCompromisedPasswordChecker { - - @Override - public Mono check(String password) { - if ("password".equals(password)) { - return Mono.just(new CompromisedPasswordDecision(true)); - } - return Mono.just(new CompromisedPasswordDecision(false)); - } - - } - - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.PARAMETER) - @AuthenticationPrincipal(expression = "#this.equals('{value}')") - @interface IsUser { - - String value() default "user"; - - } - - @Target({ ElementType.PARAMETER }) - @Retention(RetentionPolicy.RUNTIME) - @CurrentSecurityContext(expression = "authentication.{property}") - @interface CurrentAuthenticationProperty { - - String property(); - - } - - @RestController - static class TestController { - - @GetMapping("/hi") - String ifUser(@IsUser("harold") boolean isHarold) { - if (isHarold) { - return "Hi, Harold!"; - } - else { - return "Hi, Stranger!"; - } - } - - @GetMapping("/hello") - String getCurrentAuthenticationProperty( - @CurrentAuthenticationProperty(property = "principal") String principal) { - return principal; - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class MetaAnnotationPlaceholderConfig { - - @Bean - SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) - .httpBasic(Customizer.withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - ReactiveUserDetailsService userDetailsService() { - return new MapReactiveUserDetailsService( - User.withUsername("user").password("password").authorities("app").build()); - } - - @Bean - TestController testController() { - return new TestController(); - } - - @Bean - AnnotationTemplateExpressionDefaults templateExpressionDefaults() { - return new AnnotationTemplateExpressionDefaults(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class ObservationRegistryConfig { - - private ObservationHandler handler = mock(ObservationHandler.class); - - @Bean - SecurityWebFilterChain app(ServerHttpSecurity http) throws Exception { - http.httpBasic(withDefaults()).authorizeExchange((authorize) -> authorize.anyExchange().authenticated()); - return http.build(); - } - - @Bean - ReactiveUserDetailsService userDetailsService() { - return new MapReactiveUserDetailsService( - User.withDefaultPasswordEncoder().username("user").password("password").authorities("app").build()); - } - - @Bean - ObservationHandler observationHandler() { - return this.handler; - } - - @Bean - ObservationRegistry observationRegistry() { - given(this.handler.supportsContext(any())).willReturn(true); - ObservationRegistry registry = ObservationRegistry.create(); - registry.observationConfig().observationHandler(this.handler); - return registry; - } - - } - - @EnableRSocketSecurity - static class RSocketSecurityConfig { - - @Bean - RSocketMessageHandler messageHandler() { - return new RSocketMessageHandler(); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableWebFlux - @EnableWebFluxSecurity - @Import(UserDetailsConfig.class) - static class AuthorizeExchangeCustomizerBeanConfig { - - @Bean - SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - return http.build(); - } - - @Bean - static Customizer authz() { - return mock(Customizer.class); - } - - } - - @Configuration(proxyBeanMethods = false) - @Import(AuthorizeExchangeCustomizerBeanConfig.class) - static class MultiAuthorizeExchangeCustomizerBeanConfig { - - @Bean - @Order(Ordered.HIGHEST_PRECEDENCE) - Customizer authz0() { - return mock(Customizer.class); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableWebFlux - @EnableWebFluxSecurity - @Import(UserDetailsConfig.class) - static class ServerHttpSecurityCustomizerConfig { - - @Bean - SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - return http.build(); - } - - @Bean - static Customizer httpSecurityCustomizer() { - return mock(Customizer.class); - } - - } - - @Configuration(proxyBeanMethods = false) - @Import(ServerHttpSecurityCustomizerConfig.class) - static class MultiServerHttpSecurityCustomizerConfig { - - @Bean - @Order(Ordered.HIGHEST_PRECEDENCE) - static Customizer httpSecurityCustomizer0() { - return mock(Customizer.class); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfigurationTests.java deleted file mode 100644 index 798800c09b9..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/reactive/WebFluxSecurityConfigurationTests.java +++ /dev/null @@ -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.annotation.web.reactive; - -import java.util.Collections; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import reactor.core.publisher.Mono; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpStatus; -import org.springframework.lang.NonNull; -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.mock.web.server.MockServerWebExchange; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration; -import org.springframework.security.web.server.WebFilterChainProxy; -import org.springframework.security.web.server.firewall.HttpStatusExchangeRejectedHandler; -import org.springframework.security.web.server.firewall.ServerExchangeRejectedHandler; -import org.springframework.security.web.server.firewall.ServerWebExchangeFirewall; -import org.springframework.web.server.handler.DefaultWebFilterChain; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link WebFluxSecurityConfiguration}. - * - * @author Eleftheria Stein - */ -@ExtendWith(SpringTestContextExtension.class) -public class WebFluxSecurityConfigurationTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - public void loadConfigWhenReactiveUserDetailsServiceConfiguredThenWebFilterChainProxyExists() { - this.spring - .register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, - WebFluxSecurityConfiguration.class) - .autowire(); - WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class); - assertThat(webFilterChainProxy).isNotNull(); - } - - @Test - void loadConfigWhenDefaultThenFirewalled() throws Exception { - this.spring - .register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, - WebFluxSecurityConfiguration.class) - .autowire(); - WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class); - MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/;/").build()); - DefaultWebFilterChain chain = emptyChain(); - webFilterChainProxy.filter(exchange, chain).block(); - assertThat(exchange.getResponse().getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST); - } - - @Test - void loadConfigWhenCustomRejectedHandler() throws Exception { - this.spring - .register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, - WebFluxSecurityConfiguration.class, CustomServerExchangeRejectedHandlerConfig.class) - .autowire(); - WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class); - MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/;/").build()); - DefaultWebFilterChain chain = emptyChain(); - webFilterChainProxy.filter(exchange, chain).block(); - assertThat(exchange.getResponse().getStatusCode()) - .isEqualTo(CustomServerExchangeRejectedHandlerConfig.EXPECTED_STATUS); - } - - @Test - void loadConfigWhenFirewallBeanThenCustomized() throws Exception { - this.spring - .register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, - WebFluxSecurityConfiguration.class, NoOpFirewallConfig.class) - .autowire(); - WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class); - MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/;/").build()); - DefaultWebFilterChain chain = emptyChain(); - webFilterChainProxy.filter(exchange, chain).block(); - assertThat(exchange.getResponse().getStatusCode()).isNotEqualTo(HttpStatus.BAD_REQUEST); - } - - @Test - public void loadConfigWhenBeanProxyingEnabledAndSubclassThenWebFilterChainProxyExists() { - this.spring - .register(ServerHttpSecurityConfiguration.class, ReactiveAuthenticationTestConfiguration.class, - WebFluxSecurityConfigurationTests.SubclassConfig.class) - .autowire(); - WebFilterChainProxy webFilterChainProxy = this.spring.getContext().getBean(WebFilterChainProxy.class); - assertThat(webFilterChainProxy).isNotNull(); - } - - private static @NonNull DefaultWebFilterChain emptyChain() { - return new DefaultWebFilterChain((webExchange) -> Mono.empty(), Collections.emptyList()); - } - - @Configuration - static class NoOpFirewallConfig { - - @Bean - ServerWebExchangeFirewall noOpFirewall() { - return ServerWebExchangeFirewall.INSECURE_NOOP; - } - - } - - @Configuration - static class CustomServerExchangeRejectedHandlerConfig { - - static HttpStatus EXPECTED_STATUS = HttpStatus.I_AM_A_TEAPOT; - - @Bean - ServerExchangeRejectedHandler rejectedHandler() { - return new HttpStatusExchangeRejectedHandler(EXPECTED_STATUS); - } - - } - - @Configuration - static class SubclassConfig extends WebFluxSecurityConfiguration { - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/socket/SyncExecutorSubscribableChannelPostProcessor.java b/config/src/test/java/org/springframework/security/config/annotation/web/socket/SyncExecutorSubscribableChannelPostProcessor.java deleted file mode 100644 index f1dc2fdf44b..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/socket/SyncExecutorSubscribableChannelPostProcessor.java +++ /dev/null @@ -1,43 +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.annotation.web.socket; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.messaging.support.ExecutorSubscribableChannel; - -/** - * @author Rob Winch - */ -public class SyncExecutorSubscribableChannelPostProcessor implements BeanPostProcessor { - - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof ExecutorSubscribableChannel original) { - ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel(); - channel.setInterceptors(original.getInterceptors()); - return channel; - } - return bean; - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - return bean; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/socket/TestDeferredCsrfToken.java b/config/src/test/java/org/springframework/security/config/annotation/web/socket/TestDeferredCsrfToken.java deleted file mode 100644 index 9a4fb5b97ee..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/socket/TestDeferredCsrfToken.java +++ /dev/null @@ -1,43 +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.annotation.web.socket; - -import org.springframework.security.web.csrf.CsrfToken; -import org.springframework.security.web.csrf.DeferredCsrfToken; - -/** - * @author Steve Riesenberg - */ -final class TestDeferredCsrfToken implements DeferredCsrfToken { - - private final CsrfToken csrfToken; - - TestDeferredCsrfToken(CsrfToken csrfToken) { - this.csrfToken = csrfToken; - } - - @Override - public CsrfToken get() { - return this.csrfToken; - } - - @Override - public boolean isGenerated() { - return false; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfigurationDocTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfigurationDocTests.java deleted file mode 100644 index 61e43c73ecb..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfigurationDocTests.java +++ /dev/null @@ -1,182 +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.annotation.web.socket; - -import java.util.HashMap; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageDeliveryException; -import org.springframework.messaging.handler.annotation.MessageMapping; -import org.springframework.messaging.simp.SimpMessageHeaderAccessor; -import org.springframework.messaging.simp.SimpMessageType; -import org.springframework.messaging.simp.config.MessageBrokerRegistry; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.mock.web.MockServletConfig; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager; -import org.springframework.security.web.csrf.CsrfToken; -import org.springframework.security.web.csrf.DefaultCsrfToken; -import org.springframework.stereotype.Controller; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; -import org.springframework.web.socket.config.annotation.StompEndpointRegistry; -import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -public class WebSocketMessageBrokerSecurityConfigurationDocTests { - - AnnotationConfigWebApplicationContext context; - - TestingAuthenticationToken messageUser; - - CsrfToken token; - - String sessionAttr; - - @BeforeEach - public void setup() { - this.token = new DefaultCsrfToken("header", "param", "token"); - this.sessionAttr = "sessionAttr"; - this.messageUser = new TestingAuthenticationToken("user", "pass", "ROLE_USER"); - } - - @AfterEach - public void cleanup() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void securityMappings() { - loadConfig(WebSocketSecurityConfig.class); - clientInboundChannel().send(message("/user/queue/errors", SimpMessageType.SUBSCRIBE)); - assertThatExceptionOfType(MessageDeliveryException.class) - .isThrownBy(() -> clientInboundChannel().send(message("/denyAll", SimpMessageType.MESSAGE))) - .withCauseInstanceOf(AccessDeniedException.class); - } - - private void loadConfig(Class... configs) { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.register(configs); - this.context.register(WebSocketConfig.class, SyncExecutorConfig.class); - this.context.setServletConfig(new MockServletConfig()); - this.context.refresh(); - } - - private MessageChannel clientInboundChannel() { - return this.context.getBean("clientInboundChannel", MessageChannel.class); - } - - private Message message(String destination, SimpMessageType type) { - SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(type); - return message(headers, destination); - } - - private Message message(SimpMessageHeaderAccessor headers, String destination) { - headers.setSessionId("123"); - headers.setSessionAttributes(new HashMap<>()); - if (destination != null) { - headers.setDestination(destination); - } - if (this.messageUser != null) { - headers.setUser(this.messageUser); - } - return new GenericMessage<>("hi", headers.getMessageHeaders()); - } - - @Controller - static class MyController { - - @MessageMapping("/authentication") - void authentication(@AuthenticationPrincipal String un) { - // ... do something ... - } - - } - - @Configuration - @EnableWebSocketSecurity - static class WebSocketSecurityConfig { - - @Bean - AuthorizationManager> authorizationManager( - MessageMatcherDelegatingAuthorizationManager.Builder messages) { - messages.nullDestMatcher() - .authenticated() - // <1> - .simpSubscribeDestMatchers("/user/queue/errors") - .permitAll() - // <2> - .simpDestMatchers("/app/**") - .hasRole("USER") - // <3> - .simpSubscribeDestMatchers("/user/**", "/topic/friends/*") - .hasRole("USER") // <4> - .simpTypeMatchers(SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE) - .denyAll() // <5> - .anyMessage() - .denyAll(); // <6> - return messages.build(); - } - - } - - @Configuration - @EnableWebSocketMessageBroker - static class WebSocketConfig implements WebSocketMessageBrokerConfigurer { - - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - registry.addEndpoint("/chat").withSockJS(); - } - - @Override - public void configureMessageBroker(MessageBrokerRegistry registry) { - registry.enableSimpleBroker("/queue/", "/topic/"); - registry.setApplicationDestinationPrefixes("/permitAll", "/denyAll"); - } - - @Bean - MyController myController() { - return new MyController(); - } - - } - - @Configuration - static class SyncExecutorConfig { - - @Bean - static SyncExecutorSubscribableChannelPostProcessor postProcessor() { - return new SyncExecutorSubscribableChannelPostProcessor(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfigurationTests.java deleted file mode 100644 index 7dc8e3a7e9b..00000000000 --- a/config/src/test/java/org/springframework/security/config/annotation/web/socket/WebSocketMessageBrokerSecurityConfigurationTests.java +++ /dev/null @@ -1,996 +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.annotation.web.socket; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.stream.Stream; - -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationHandler; -import io.micrometer.observation.ObservationRegistry; -import io.micrometer.observation.ObservationTextPublisher; -import jakarta.servlet.http.HttpServletRequest; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.core.MethodParameter; -import org.springframework.http.server.PathContainer; -import org.springframework.http.server.ServerHttpRequest; -import org.springframework.http.server.ServerHttpResponse; -import org.springframework.messaging.Message; -import org.springframework.messaging.MessageChannel; -import org.springframework.messaging.MessageDeliveryException; -import org.springframework.messaging.handler.annotation.MessageMapping; -import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; -import org.springframework.messaging.simp.SimpMessageHeaderAccessor; -import org.springframework.messaging.simp.SimpMessageType; -import org.springframework.messaging.simp.config.MessageBrokerRegistry; -import org.springframework.messaging.support.AbstractMessageChannel; -import org.springframework.messaging.support.ChannelInterceptor; -import org.springframework.messaging.support.GenericMessage; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockServletConfig; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.authentication.RememberMeAuthenticationToken; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.observation.SecurityObservationSettings; -import org.springframework.security.config.web.messaging.PathPatternMessageMatcherBuilderFactoryBean; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor; -import org.springframework.security.messaging.access.intercept.MessageAuthorizationContext; -import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager; -import org.springframework.security.messaging.context.SecurityContextChannelInterceptor; -import org.springframework.security.messaging.web.csrf.XorCsrfChannelInterceptor; -import org.springframework.security.web.csrf.CsrfToken; -import org.springframework.security.web.csrf.DefaultCsrfToken; -import org.springframework.security.web.csrf.DeferredCsrfToken; -import org.springframework.security.web.csrf.MissingCsrfTokenException; -import org.springframework.stereotype.Controller; -import org.springframework.test.util.ReflectionTestUtils; -import org.springframework.util.AntPathMatcher; -import org.springframework.web.HttpRequestHandler; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; -import org.springframework.web.socket.config.annotation.StompEndpointRegistry; -import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; -import org.springframework.web.socket.server.HandshakeFailureException; -import org.springframework.web.socket.server.HandshakeHandler; -import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; -import org.springframework.web.socket.sockjs.transport.handler.SockJsWebSocketHandler; -import org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession; -import org.springframework.web.util.pattern.PathPatternParser; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.springframework.security.web.csrf.CsrfTokenAssert.assertThatCsrfToken; - -public class WebSocketMessageBrokerSecurityConfigurationTests { - - private static final String XOR_CSRF_TOKEN_VALUE = "wpe7zB62-NCpcA=="; - - AnnotationConfigWebApplicationContext context; - - Authentication messageUser; - - CsrfToken token; - - String sessionAttr; - - @BeforeEach - public void setup() { - this.token = new DefaultCsrfToken("header", "param", "token"); - this.sessionAttr = "sessionAttr"; - this.messageUser = new TestingAuthenticationToken("user", "pass", "ROLE_USER"); - } - - @AfterEach - public void cleanup() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void simpleRegistryMappings() { - loadConfig(SockJsSecurityConfig.class); - clientInboundChannel().send(message("/permitAll")); - assertThatExceptionOfType(MessageDeliveryException.class) - .isThrownBy(() -> clientInboundChannel().send(message("/denyAll"))) - .withCauseInstanceOf(AccessDeniedException.class); - } - - @Test - public void annonymousSupported() { - loadConfig(SockJsSecurityConfig.class); - this.messageUser = null; - clientInboundChannel().send(message("/permitAll")); - } - - // gh-3797 - @Test - public void beanResolver() { - loadConfig(SockJsSecurityConfig.class); - this.messageUser = null; - clientInboundChannel().send(message("/beanResolver")); - } - - @Test - public void addsAuthenticationPrincipalResolver() { - loadConfig(SockJsSecurityConfig.class); - MessageChannel messageChannel = clientInboundChannel(); - Message message = message("/permitAll/authentication"); - messageChannel.send(message); - assertThat(this.context.getBean(MyController.class).authenticationPrincipal) - .isEqualTo((String) this.messageUser.getPrincipal()); - } - - @Test - public void addsAuthenticationPrincipalResolverWhenNoAuthorization() { - loadConfig(NoInboundSecurityConfig.class); - MessageChannel messageChannel = clientInboundChannel(); - Message message = message("/permitAll/authentication"); - messageChannel.send(message); - assertThat(this.context.getBean(MyController.class).authenticationPrincipal) - .isEqualTo((String) this.messageUser.getPrincipal()); - } - - @Test - public void sendMessageWhenMetaAnnotationThenParsesExpression() { - loadConfig(NoInboundSecurityConfig.class); - this.messageUser = new TestingAuthenticationToken("harold", "password", "ROLE_USER"); - clientInboundChannel().send(message("/permitAll/hi")); - assertThat(this.context.getBean(MyController.class).message).isEqualTo("Hi, Harold!"); - this.messageUser = new TestingAuthenticationToken("user", "password", "ROLE_USER"); - clientInboundChannel().send(message("/permitAll/hi")); - assertThat(this.context.getBean(MyController.class).message).isEqualTo("Hi, Stranger!"); - } - - @Test - public void addsCsrfProtectionWhenNoAuthorization() { - loadConfig(NoInboundSecurityConfig.class); - SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); - Message message = message(headers, "/authentication"); - MessageChannel messageChannel = clientInboundChannel(); - assertThatExceptionOfType(MessageDeliveryException.class).isThrownBy(() -> messageChannel.send(message)) - .withCauseInstanceOf(MissingCsrfTokenException.class); - } - - @Test - public void csrfProtectionForConnect() { - loadConfig(SockJsSecurityConfig.class); - SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); - Message message = message(headers, "/authentication"); - MessageChannel messageChannel = clientInboundChannel(); - assertThatExceptionOfType(MessageDeliveryException.class).isThrownBy(() -> messageChannel.send(message)) - .withCauseInstanceOf(MissingCsrfTokenException.class); - } - - @Test - @Disabled // to be added back in with the introduction of DSL support - public void csrfProtectionDisabledForConnect() { - loadConfig(CsrfDisabledSockJsSecurityConfig.class); - SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); - Message message = message(headers, "/permitAll/connect"); - MessageChannel messageChannel = clientInboundChannel(); - messageChannel.send(message); - } - - @Test - public void csrfProtectionDefinedByBean() { - loadConfig(SockJsProxylessSecurityConfig.class); - MessageChannel messageChannel = clientInboundChannel(); - Stream> interceptors = ((AbstractMessageChannel) messageChannel) - .getInterceptors() - .stream() - .map(ChannelInterceptor::getClass); - assertThat(interceptors).contains(XorCsrfChannelInterceptor.class); - } - - @Test - public void messagesConnectUseCsrfTokenHandshakeInterceptor() throws Exception { - loadConfig(SockJsSecurityConfig.class); - SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); - Message message = message(headers, "/authentication"); - MockHttpServletRequest request = sockjsHttpRequest("/chat"); - HttpRequestHandler handler = handler(request); - handler.handleRequest(request, new MockHttpServletResponse()); - assertHandshake(request); - } - - @Test - public void messagesConnectUseCsrfTokenHandshakeInterceptorMultipleMappings() throws Exception { - loadConfig(SockJsSecurityConfig.class); - SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); - Message message = message(headers, "/authentication"); - MockHttpServletRequest request = sockjsHttpRequest("/other"); - HttpRequestHandler handler = handler(request); - handler.handleRequest(request, new MockHttpServletResponse()); - assertHandshake(request); - } - - @Test - public void messagesConnectWebSocketUseCsrfTokenHandshakeInterceptor() throws Exception { - loadConfig(WebSocketSecurityConfig.class); - SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); - Message message = message(headers, "/authentication"); - MockHttpServletRequest request = websocketHttpRequest("/websocket"); - HttpRequestHandler handler = handler(request); - handler.handleRequest(request, new MockHttpServletResponse()); - assertHandshake(request); - } - - @Test - public void messagesContextWebSocketUseSecurityContextHolderStrategy() { - loadConfig(WebSocketSecurityConfig.class, SecurityContextChangedListenerConfig.class); - SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); - headers.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE); - Message message = message(headers, "/authenticated"); - headers.getSessionAttributes().put(CsrfToken.class.getName(), this.token); - MessageChannel messageChannel = clientInboundChannel(); - messageChannel.send(message); - verify(this.context.getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); - } - - @Test - public void msmsRegistryCustomPatternMatcher() { - loadConfig(MsmsRegistryCustomPatternMatcherConfig.class); - clientInboundChannel().send(message("/app/a.b")); - assertThatExceptionOfType(MessageDeliveryException.class) - .isThrownBy(() -> clientInboundChannel().send(message("/app/a.b.c"))) - .withCauseInstanceOf(AccessDeniedException.class); - } - - @Test - public void overrideMsmsRegistryCustomPatternMatcher() { - loadConfig(OverrideMsmsRegistryCustomPatternMatcherConfig.class); - clientInboundChannel().send(message("/app/a/b")); - assertThatExceptionOfType(MessageDeliveryException.class) - .isThrownBy(() -> clientInboundChannel().send(message("/app/a/b/c"))) - .withCauseInstanceOf(AccessDeniedException.class); - } - - @Test - public void defaultPatternMatcher() { - loadConfig(DefaultPatternMatcherConfig.class); - clientInboundChannel().send(message("/app/a/b")); - assertThatExceptionOfType(MessageDeliveryException.class) - .isThrownBy(() -> clientInboundChannel().send(message("/app/a/b/c"))) - .withCauseInstanceOf(AccessDeniedException.class); - } - - @Test - public void customExpression() { - loadConfig(CustomExpressionConfig.class); - clientInboundChannel().send(message("/denyRob")); - this.messageUser = new TestingAuthenticationToken("rob", "password", "ROLE_USER"); - assertThatExceptionOfType(MessageDeliveryException.class) - .isThrownBy(() -> clientInboundChannel().send(message("/denyRob"))) - .withCauseInstanceOf(AccessDeniedException.class); - } - - @Test - public void channelSecurityInterceptorUsesMetadataSourceBeanWhenProxyingDisabled() { - loadConfig(SockJsProxylessSecurityConfig.class); - AbstractMessageChannel messageChannel = clientInboundChannel(); - AuthorizationManager> authorizationManager = this.context.getBean(AuthorizationManager.class); - for (ChannelInterceptor interceptor : messageChannel.getInterceptors()) { - if (interceptor instanceof AuthorizationChannelInterceptor) { - assertThat(ReflectionTestUtils.getField(interceptor, "preSendAuthorizationManager")) - .isSameAs(authorizationManager); - return; - } - } - fail("did not find AuthorizationChannelInterceptor"); - } - - @Test - public void securityContextChannelInterceptorDefinedByBean() { - loadConfig(SockJsProxylessSecurityConfig.class); - MessageChannel messageChannel = clientInboundChannel(); - Stream> interceptors = ((AbstractMessageChannel) messageChannel) - .getInterceptors() - .stream() - .map(ChannelInterceptor::getClass); - assertThat(interceptors).contains(SecurityContextChannelInterceptor.class); - } - - @Test - public void inboundChannelSecurityDefinedByBean() { - loadConfig(SockJsProxylessSecurityConfig.class); - MessageChannel messageChannel = clientInboundChannel(); - Stream> interceptors = ((AbstractMessageChannel) messageChannel) - .getInterceptors() - .stream() - .map(ChannelInterceptor::getClass); - assertThat(interceptors).contains(AuthorizationChannelInterceptor.class); - } - - @Test - public void sendMessageWhenFullyAuthenticatedConfiguredAndRememberMeTokenThenAccessDeniedException() { - loadConfig(WebSocketSecurityConfig.class); - this.messageUser = new RememberMeAuthenticationToken("key", "user", - AuthorityUtils.createAuthorityList("ROLE_USER")); - assertThatExceptionOfType(MessageDeliveryException.class) - .isThrownBy(() -> clientInboundChannel().send(message("/fullyAuthenticated"))) - .withCauseInstanceOf(AccessDeniedException.class); - } - - @Test - public void sendMessageWhenFullyAuthenticatedConfiguredAndUserThenPasses() { - loadConfig(WebSocketSecurityConfig.class); - clientInboundChannel().send(message("/fullyAuthenticated")); - } - - @Test - public void sendMessageWhenRememberMeConfiguredAndNoUserThenAccessDeniedException() { - loadConfig(WebSocketSecurityConfig.class); - this.messageUser = null; - assertThatExceptionOfType(MessageDeliveryException.class) - .isThrownBy(() -> clientInboundChannel().send(message("/rememberMe"))) - .withCauseInstanceOf(AccessDeniedException.class); - } - - @Test - public void sendMessageWhenRememberMeConfiguredAndRememberMeTokenThenPasses() { - loadConfig(WebSocketSecurityConfig.class); - this.messageUser = new RememberMeAuthenticationToken("key", "user", - AuthorityUtils.createAuthorityList("ROLE_USER")); - clientInboundChannel().send(message("/rememberMe")); - } - - @Test - public void sendMessageWhenAnonymousConfiguredAndAnonymousUserThenPasses() { - loadConfig(WebSocketSecurityConfig.class); - this.messageUser = new AnonymousAuthenticationToken("key", "user", - AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); - clientInboundChannel().send(message("/anonymous")); - } - - @Test - public void sendMessageWhenObservationRegistryThenObserves() { - loadConfig(WebSocketSecurityConfig.class, ObservationRegistryConfig.class); - SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); - headers.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE); - Message message = message(headers, "/authenticated"); - headers.getSessionAttributes().put(CsrfToken.class.getName(), this.token); - clientInboundChannel().send(message); - ObservationHandler observationHandler = this.context.getBean(ObservationHandler.class); - verify(observationHandler).onStart(any()); - verify(observationHandler).onStop(any()); - headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); - headers.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE); - message = message(headers, "/denyAll"); - headers.getSessionAttributes().put(CsrfToken.class.getName(), this.token); - try { - clientInboundChannel().send(message); - } - catch (MessageDeliveryException ex) { - // okay - } - verify(observationHandler).onError(any()); - } - - @Test - public void sendMessageWhenExcludeAuthorizationObservationsThenUnobserved() { - loadConfig(WebSocketSecurityConfig.class, ObservationRegistryConfig.class, SelectableObservationsConfig.class); - SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); - headers.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE); - Message message = message(headers, "/authenticated"); - headers.getSessionAttributes().put(CsrfToken.class.getName(), this.token); - clientInboundChannel().send(message); - ObservationHandler observationHandler = this.context.getBean(ObservationHandler.class); - headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT); - headers.setNativeHeader(this.token.getHeaderName(), XOR_CSRF_TOKEN_VALUE); - message = message(headers, "/denyAll"); - headers.getSessionAttributes().put(CsrfToken.class.getName(), this.token); - try { - clientInboundChannel().send(message); - } - catch (MessageDeliveryException ex) { - // okay - } - verifyNoInteractions(observationHandler); - } - - // gh-16011 - @Test - public void enableWebSocketSecurityWhenWebSocketSecurityUsedThenAutowires() { - loadConfig(WithWebSecurity.class); - } - - private void assertHandshake(HttpServletRequest request) { - TestHandshakeHandler handshakeHandler = this.context.getBean(TestHandshakeHandler.class); - assertThatCsrfToken(handshakeHandler.attributes.get(CsrfToken.class.getName())).isEqualTo(this.token); - assertThat(handshakeHandler.attributes).containsEntry(this.sessionAttr, - request.getSession().getAttribute(this.sessionAttr)); - } - - private HttpRequestHandler handler(HttpServletRequest request) throws Exception { - HandlerMapping handlerMapping = this.context.getBean(HandlerMapping.class); - return (HttpRequestHandler) handlerMapping.getHandler(request).getHandler(); - } - - private MockHttpServletRequest websocketHttpRequest(String mapping) { - MockHttpServletRequest request = sockjsHttpRequest(mapping); - request.setRequestURI(mapping); - return request; - } - - private MockHttpServletRequest sockjsHttpRequest(String mapping) { - MockHttpServletRequest request = new MockHttpServletRequest("GET", ""); - request.setMethod("GET"); - request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/289/tpyx6mde/websocket"); - request.setRequestURI(mapping + "/289/tpyx6mde/websocket"); - request.getSession().setAttribute(this.sessionAttr, "sessionValue"); - request.setAttribute(DeferredCsrfToken.class.getName(), new TestDeferredCsrfToken(this.token)); - return request; - } - - private Message message(String destination) { - SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(); - return message(headers, destination); - } - - private Message message(SimpMessageHeaderAccessor headers, String destination) { - headers.setSessionId("123"); - headers.setSessionAttributes(new HashMap<>()); - if (destination != null) { - headers.setDestination(destination); - } - if (this.messageUser != null) { - headers.setUser(this.messageUser); - } - return new GenericMessage<>("hi", headers.getMessageHeaders()); - } - - private T clientInboundChannel() { - return (T) this.context.getBean("clientInboundChannel", MessageChannel.class); - } - - private void loadConfig(Class... configs) { - this.context = new AnnotationConfigWebApplicationContext(); - this.context.setAllowBeanDefinitionOverriding(false); - this.context.register(configs); - this.context.setServletConfig(new MockServletConfig()); - this.context.refresh(); - } - - @Configuration - @EnableWebSocketMessageBroker - @EnableWebSocketSecurity - @Import(SyncExecutorConfig.class) - static class MsmsRegistryCustomPatternMatcherConfig implements WebSocketMessageBrokerConfigurer { - - @Bean - PathPatternMessageMatcherBuilderFactoryBean messageMatcherBuilder() { - PathPatternParser parser = new PathPatternParser(); - parser.setPathOptions(PathContainer.Options.MESSAGE_ROUTE); - return new PathPatternMessageMatcherBuilderFactoryBean(parser); - } - - // @formatter:off - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - registry - .addEndpoint("/other") - .setHandshakeHandler(testHandshakeHandler()); - } - // @formatter:on - - @Override - public void configureMessageBroker(MessageBrokerRegistry registry) { - registry.enableSimpleBroker("/queue/", "/topic/"); - registry.setApplicationDestinationPrefixes("/app"); - } - - // @formatter:off - @Bean - AuthorizationManager> authorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) { - messages - .simpDestMatchers("/app/a.*").permitAll() - .anyMessage().denyAll(); - - return messages.build(); - } - // @formatter:on - - @Bean - TestHandshakeHandler testHandshakeHandler() { - return new TestHandshakeHandler(); - } - - } - - @Configuration - @EnableWebSocketMessageBroker - @EnableWebSocketSecurity - @Import(SyncExecutorConfig.class) - static class OverrideMsmsRegistryCustomPatternMatcherConfig implements WebSocketMessageBrokerConfigurer { - - // @formatter:off - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - registry - .addEndpoint("/other") - .setHandshakeHandler(testHandshakeHandler()); - } - // @formatter:on - - @Override - public void configureMessageBroker(MessageBrokerRegistry registry) { - registry.setPathMatcher(new AntPathMatcher(".")); - registry.enableSimpleBroker("/queue/", "/topic/"); - registry.setApplicationDestinationPrefixes("/app"); - } - - // @formatter:off - @Bean - AuthorizationManager> authorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) { - messages - .simpDestMatchers("/app/a/*").permitAll() - .anyMessage().denyAll(); - return messages.build(); - } - // @formatter:on - - @Bean - TestHandshakeHandler testHandshakeHandler() { - return new TestHandshakeHandler(); - } - - } - - @Configuration - @EnableWebSocketMessageBroker - @EnableWebSocketSecurity - @Import(SyncExecutorConfig.class) - static class DefaultPatternMatcherConfig implements WebSocketMessageBrokerConfigurer { - - // @formatter:off - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - registry - .addEndpoint("/other") - .setHandshakeHandler(testHandshakeHandler()); - } - // @formatter:on - - @Override - public void configureMessageBroker(MessageBrokerRegistry registry) { - registry.enableSimpleBroker("/queue/", "/topic/"); - registry.setApplicationDestinationPrefixes("/app"); - } - - // @formatter:off - @Bean - AuthorizationManager> authorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) { - messages - .simpDestMatchers("/app/a/*").permitAll() - .anyMessage().denyAll(); - - return messages.build(); - } - // @formatter:on - - @Bean - TestHandshakeHandler testHandshakeHandler() { - return new TestHandshakeHandler(); - } - - } - - @Configuration - @EnableWebSocketMessageBroker - @EnableWebSocketSecurity - @Import(SyncExecutorConfig.class) - static class CustomExpressionConfig implements WebSocketMessageBrokerConfigurer { - - // @formatter:off - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - registry - .addEndpoint("/other") - .setHandshakeHandler(testHandshakeHandler()); - } - // @formatter:on - - @Override - public void configureMessageBroker(MessageBrokerRegistry registry) { - registry.enableSimpleBroker("/queue/", "/topic/"); - registry.setApplicationDestinationPrefixes("/app"); - } - - @Bean - AuthorizationManager> authorizationManager() { - return (authentication, message) -> { - Authentication auth = authentication.get(); - return new AuthorizationDecision(auth != null && !"rob".equals(auth.getName())); - }; - } - - @Bean - TestHandshakeHandler testHandshakeHandler() { - return new TestHandshakeHandler(); - } - - } - - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.PARAMETER) - @AuthenticationPrincipal(expression = "#this.equals('{value}')") - @interface IsUser { - - String value() default "user"; - - } - - @Controller - static class MyController { - - String authenticationPrincipal; - - MyCustomArgument myCustomArgument; - - String message; - - @MessageMapping("/authentication") - void authentication(@AuthenticationPrincipal String un) { - this.authenticationPrincipal = un; - } - - @MessageMapping("/myCustom") - void myCustom(MyCustomArgument myCustomArgument) { - this.myCustomArgument = myCustomArgument; - } - - @MessageMapping("/hi") - void sayHello(@IsUser("harold") boolean isHarold) { - this.message = isHarold ? "Hi, Harold!" : "Hi, Stranger!"; - } - - } - - static class MyCustomArgument { - - MyCustomArgument(String notDefaultConstr) { - } - - } - - static class MyCustomArgumentResolver implements HandlerMethodArgumentResolver { - - @Override - public boolean supportsParameter(MethodParameter parameter) { - return parameter.getParameterType().isAssignableFrom(MyCustomArgument.class); - } - - @Override - public Object resolveArgument(MethodParameter parameter, Message message) { - return new MyCustomArgument(""); - } - - } - - static class TestHandshakeHandler implements HandshakeHandler { - - Map attributes; - - @Override - public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, - Map attributes) throws HandshakeFailureException { - this.attributes = attributes; - if (wsHandler instanceof SockJsWebSocketHandler sockJs) { - // work around SPR-12716 - WebSocketServerSockJsSession session = (WebSocketServerSockJsSession) ReflectionTestUtils - .getField(sockJs, "sockJsSession"); - this.attributes = session.getAttributes(); - } - return true; - } - - } - - @Configuration - @EnableWebSocketSecurity - @EnableWebSocketMessageBroker - @Import(SyncExecutorConfig.class) - static class SockJsSecurityConfig implements WebSocketMessageBrokerConfigurer { - - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - // @formatter:off - registry.addEndpoint("/other").setHandshakeHandler(testHandshakeHandler()) - .withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor()); - registry.addEndpoint("/chat").setHandshakeHandler(testHandshakeHandler()) - .withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor()); - // @formatter:on - } - - // @formatter:off - @Bean - AuthorizationManager> authorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages, - SecurityCheck security) { - AuthorizationManager> beanResolver = - (authentication, context) -> new AuthorizationDecision(security.check()); - messages - .simpDestMatchers("/permitAll/**").permitAll() - .simpDestMatchers("/beanResolver/**").access(beanResolver) - .anyMessage().denyAll(); - return messages.build(); - } - // @formatter:on - - @Override - public void configureMessageBroker(MessageBrokerRegistry registry) { - registry.enableSimpleBroker("/queue/", "/topic/"); - registry.setApplicationDestinationPrefixes("/permitAll", "/denyAll"); - } - - @Bean - MyController myController() { - return new MyController(); - } - - @Bean - TestHandshakeHandler testHandshakeHandler() { - return new TestHandshakeHandler(); - } - - @Bean - SecurityCheck security() { - return new SecurityCheck(); - } - - static class SecurityCheck { - - private boolean check; - - boolean check() { - this.check = !this.check; - return this.check; - } - - } - - } - - @Configuration - @EnableWebSocketSecurity - @EnableWebSocketMessageBroker - @Import(SyncExecutorConfig.class) - static class NoInboundSecurityConfig implements WebSocketMessageBrokerConfigurer { - - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - // @formatter:off - registry.addEndpoint("/other") - .withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor()); - registry.addEndpoint("/chat") - .withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor()); - // @formatter:on - } - - @Override - public void configureMessageBroker(MessageBrokerRegistry registry) { - registry.enableSimpleBroker("/queue/", "/topic/"); - registry.setApplicationDestinationPrefixes("/permitAll", "/denyAll"); - } - - @Bean - MyController myController() { - return new MyController(); - } - - @Bean - AnnotationTemplateExpressionDefaults templateExpressionDefaults() { - return new AnnotationTemplateExpressionDefaults(); - } - - } - - @Configuration - @Import(SockJsSecurityConfig.class) - static class CsrfDisabledSockJsSecurityConfig { - - @Bean - Consumer> channelInterceptorCustomizer() { - return (interceptors) -> interceptors.remove(1); - } - - } - - @Configuration - @EnableWebSocketSecurity - @EnableWebSocketMessageBroker - @Import(SyncExecutorConfig.class) - static class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer { - - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - // @formatter:off - registry.addEndpoint("/websocket") - .setHandshakeHandler(testHandshakeHandler()) - .addInterceptors(new HttpSessionHandshakeInterceptor()); - // @formatter:on - } - - @Bean - AuthorizationManager> authorizationManager( - MessageMatcherDelegatingAuthorizationManager.Builder messages) { - // @formatter:off - messages - .simpDestMatchers("/permitAll/**").permitAll() - .simpDestMatchers("/authenticated/**").authenticated() - .simpDestMatchers("/fullyAuthenticated/**").fullyAuthenticated() - .simpDestMatchers("/rememberMe/**").rememberMe() - .simpDestMatchers("/anonymous/**").anonymous() - .anyMessage().denyAll(); - // @formatter:on - return messages.build(); - } - - @Bean - TestHandshakeHandler testHandshakeHandler() { - return new TestHandshakeHandler(); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableWebSocketSecurity - @EnableWebSocketMessageBroker - @Import(SyncExecutorConfig.class) - static class SockJsProxylessSecurityConfig implements WebSocketMessageBrokerConfigurer { - - private ApplicationContext context; - - @Override - public void registerStompEndpoints(StompEndpointRegistry registry) { - // @formatter:off - registry.addEndpoint("/chat") - .setHandshakeHandler(this.context.getBean(TestHandshakeHandler.class)) - .withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor()); - // @formatter:on - } - - @Autowired - void setContext(ApplicationContext context) { - this.context = context; - } - - // @formatter:off - @Bean - AuthorizationManager> authorizationManager(MessageMatcherDelegatingAuthorizationManager.Builder messages) { - messages - .anyMessage().denyAll(); - return messages.build(); - } - // @formatter:on - - @Bean - TestHandshakeHandler testHandshakeHandler() { - return new TestHandshakeHandler(); - } - - } - - @Configuration(proxyBeanMethods = false) - @EnableWebSecurity - @Import(WebSocketSecurityConfig.class) - static class WithWebSecurity { - - } - - @Configuration - static class SyncExecutorConfig { - - @Bean - static SyncExecutorSubscribableChannelPostProcessor postProcessor() { - return new SyncExecutorSubscribableChannelPostProcessor(); - } - - } - - @Configuration - static class ObservationRegistryConfig { - - private final ObservationRegistry registry = ObservationRegistry.create(); - - private final ObservationHandler handler = spy(new ObservationTextPublisher()); - - @Bean - ObservationRegistry observationRegistry() { - return this.registry; - } - - @Bean - ObservationHandler observationHandler() { - return this.handler; - } - - @Bean - ObservationRegistryPostProcessor observationRegistryPostProcessor( - ObjectProvider> handler) { - return new ObservationRegistryPostProcessor(handler); - } - - } - - static class ObservationRegistryPostProcessor implements BeanPostProcessor { - - private final ObjectProvider> handler; - - ObservationRegistryPostProcessor(ObjectProvider> handler) { - this.handler = handler; - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { - if (bean instanceof ObservationRegistry registry) { - registry.observationConfig().observationHandler(this.handler.getObject()); - } - return bean; - } - - } - - @Configuration - static class SelectableObservationsConfig { - - @Bean - SecurityObservationSettings observabilityDefaults() { - return SecurityObservationSettings.withDefaults().shouldObserveAuthorizations(false).build(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/aot/hint/OAuth2LoginRuntimeHintsTests.java b/config/src/test/java/org/springframework/security/config/aot/hint/OAuth2LoginRuntimeHintsTests.java deleted file mode 100644 index 2f290a67a7a..00000000000 --- a/config/src/test/java/org/springframework/security/config/aot/hint/OAuth2LoginRuntimeHintsTests.java +++ /dev/null @@ -1,55 +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.aot.hint; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.aot.hint.MemberCategory; -import org.springframework.aot.hint.RuntimeHints; -import org.springframework.aot.hint.RuntimeHintsRegistrar; -import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; -import org.springframework.core.io.support.SpringFactoriesLoader; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.util.ClassUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link OAuth2LoginRuntimeHints} - * - * @author Marcus Da Coregio - */ -class OAuth2LoginRuntimeHintsTests { - - private final RuntimeHints hints = new RuntimeHints(); - - @BeforeEach - void setup() { - SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories") - .load(RuntimeHintsRegistrar.class) - .forEach((registrar) -> registrar.registerHints(this.hints, ClassUtils.getDefaultClassLoader())); - } - - @Test - void jwtDecoderHasHints() { - assertThat(RuntimeHintsPredicates.reflection() - .onType(JwtDecoder.class) - .withMemberCategories(MemberCategory.INVOKE_PUBLIC_METHODS)).accepts(this.hints); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/aot/hint/WebMvcSecurityConfigurationRuntimeHintsTests.java b/config/src/test/java/org/springframework/security/config/aot/hint/WebMvcSecurityConfigurationRuntimeHintsTests.java deleted file mode 100644 index c0cd9ab5fc6..00000000000 --- a/config/src/test/java/org/springframework/security/config/aot/hint/WebMvcSecurityConfigurationRuntimeHintsTests.java +++ /dev/null @@ -1,56 +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.aot.hint; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -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.aot.hint.predicate.RuntimeHintsPredicates; -import org.springframework.core.io.support.SpringFactoriesLoader; -import org.springframework.util.ClassUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link WebMvcSecurityConfigurationRuntimeHints} - * - * @author Marcus da Coregio - */ -class WebMvcSecurityConfigurationRuntimeHintsTests { - - private final RuntimeHints hints = new RuntimeHints(); - - @BeforeEach - void setup() { - SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories") - .load(RuntimeHintsRegistrar.class) - .forEach((registrar) -> registrar.registerHints(this.hints, ClassUtils.getDefaultClassLoader())); - } - - @Test - void compositeFilterChainProxyHasHints() { - assertThat(RuntimeHintsPredicates.reflection() - .onType(TypeReference - .of("org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration$CompositeFilterChainProxy")) - .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.hints); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/aot/hint/WebSecurityConfigurationRuntimeHintsTests.java b/config/src/test/java/org/springframework/security/config/aot/hint/WebSecurityConfigurationRuntimeHintsTests.java deleted file mode 100644 index ccb3fe74e5b..00000000000 --- a/config/src/test/java/org/springframework/security/config/aot/hint/WebSecurityConfigurationRuntimeHintsTests.java +++ /dev/null @@ -1,59 +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.aot.hint; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -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.aot.hint.predicate.RuntimeHintsPredicates; -import org.springframework.core.io.support.SpringFactoriesLoader; -import org.springframework.util.ClassUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link WebSecurityConfigurationRuntimeHints} - * - * @author Marcus da Coregio - */ -class WebSecurityConfigurationRuntimeHintsTests { - - private final RuntimeHints hints = new RuntimeHints(); - - @BeforeEach - void setup() { - SpringFactoriesLoader.forResourceLocation("META-INF/spring/aot.factories") - .load(RuntimeHintsRegistrar.class) - .forEach((registrar) -> registrar.registerHints(this.hints, ClassUtils.getDefaultClassLoader())); - } - - @Test - void compositeFilterChainProxyHasHints() { - assertThat(RuntimeHintsPredicates.reflection() - .onType(TypeReference - .of("org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration$CompositeFilterChainProxy")) - .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.hints); - assertThat(RuntimeHintsPredicates.reflection() - .onType(TypeReference.of("org.springframework.web.filter.ServletRequestPathFilter")) - .withMemberCategory(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS)).accepts(this.hints); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/authentication/AuthenticationConfigurationGh3935Tests.java b/config/src/test/java/org/springframework/security/config/authentication/AuthenticationConfigurationGh3935Tests.java deleted file mode 100644 index b69c0bd7612..00000000000 --- a/config/src/test/java/org/springframework/security/config/authentication/AuthenticationConfigurationGh3935Tests.java +++ /dev/null @@ -1,119 +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.authentication; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; -import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * @author Rob Winch - */ -@ExtendWith(SpringExtension.class) -@ContextConfiguration -public class AuthenticationConfigurationGh3935Tests { - - @Autowired - FilterChainProxy springSecurityFilterChain; - - @Autowired - UserDetailsService uds; - - @Autowired - BootGlobalAuthenticationConfigurationAdapter adapter; - - // gh-3935 - @Test - public void loads() { - assertThat(this.springSecurityFilterChain).isNotNull(); - } - - @Test - public void delegateUsesExisitingAuthentication() { - String username = "user"; - String password = "password"; - given(this.uds.loadUserByUsername(username)).willReturn(PasswordEncodedUser.user()); - AuthenticationManager authenticationManager = this.adapter.authenticationManager; - assertThat(authenticationManager).isNotNull(); - Authentication auth = authenticationManager - .authenticate(UsernamePasswordAuthenticationToken.unauthenticated(username, password)); - verify(this.uds).loadUserByUsername(username); - assertThat(auth.getPrincipal()).isEqualTo(PasswordEncodedUser.user()); - } - - @Configuration - @EnableWebSecurity - static class WebSecurity { - - } - - static class BootGlobalAuthenticationConfigurationAdapter extends GlobalAuthenticationConfigurerAdapter { - - private final ApplicationContext context; - - private AuthenticationManager authenticationManager; - - @Autowired - BootGlobalAuthenticationConfigurationAdapter(ApplicationContext context) { - this.context = context; - } - - @Override - public void init(AuthenticationManagerBuilder auth) { - AuthenticationConfiguration configuration = this.context.getBean(AuthenticationConfiguration.class); - this.authenticationManager = configuration.getAuthenticationManager(); - } - - } - - @Configuration - static class AutoConfig { - - @Bean - static BootGlobalAuthenticationConfigurationAdapter adapter(ApplicationContext context) { - return new BootGlobalAuthenticationConfigurationAdapter(context); - } - - @Bean - UserDetailsService userDetailsService() { - return mock(UserDetailsService.class); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/authentication/AuthenticationManagerBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/authentication/AuthenticationManagerBeanDefinitionParserTests.java deleted file mode 100644 index 3a48c9a3d99..00000000000 --- a/config/src/test/java/org/springframework/security/config/authentication/AuthenticationManagerBeanDefinitionParserTests.java +++ /dev/null @@ -1,165 +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.authentication; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationListener; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.security.authentication.AuthenticationEventPublisher; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; -import org.springframework.security.authentication.ProviderManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.authentication.event.AbstractAuthenticationEvent; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.config.util.InMemoryXmlWebApplicationContext; -import org.springframework.security.util.FieldUtils; -import org.springframework.test.web.servlet.MockMvc; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Luke Taylor - */ -@ExtendWith(SpringTestContextExtension.class) -public class AuthenticationManagerBeanDefinitionParserTests { - - // @formatter:off - private static final String CONTEXT = "" - + " " - + " " - + " " - + " " - + " " - + ""; - // @formatter:on - - // Issue #7282 - // @formatter:off - private static final String CONTEXT_MULTI = "" - + " " - + " " - + " " - + " " - + " " - + ""; - // @formatter:on - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - // SEC-1225 - public void providersAreRegisteredAsTopLevelBeans() { - ConfigurableApplicationContext context = this.spring.context(CONTEXT).getContext(); - assertThat(context.getBeansOfType(AuthenticationProvider.class)).hasSize(1); - } - - @Test - public void eventPublishersAreRegisteredAsTopLevelBeans() { - ConfigurableApplicationContext context = this.spring.context(CONTEXT).getContext(); - assertThat(context.getBeansOfType(AuthenticationEventPublisher.class)).hasSize(1); - } - - @Test - public void onlyOneEventPublisherIsRegisteredForMultipleAuthenticationManagers() { - ConfigurableApplicationContext context = this.spring.context(CONTEXT + '\n' + CONTEXT_MULTI).getContext(); - assertThat(context.getBeansOfType(AuthenticationEventPublisher.class)).hasSize(1); - } - - @Test - // gh-8767 - public void multipleAuthenticationManagersAndDisableBeanDefinitionOverridingThenNoException() { - InMemoryXmlWebApplicationContext xmlContext = new InMemoryXmlWebApplicationContext( - CONTEXT + '\n' + CONTEXT_MULTI); - xmlContext.setAllowBeanDefinitionOverriding(false); - ConfigurableApplicationContext context = this.spring.context(xmlContext).getContext(); - assertThat(context.getBeansOfType(AuthenticationManager.class)).hasSize(2); - } - - @Test - public void eventsArePublishedByDefault() throws Exception { - ConfigurableApplicationContext appContext = this.spring.context(CONTEXT).getContext(); - AuthListener listener = new AuthListener(); - appContext.addApplicationListener(listener); - ProviderManager pm = (ProviderManager) appContext.getBeansOfType(ProviderManager.class).values().toArray()[0]; - Object eventPublisher = FieldUtils.getFieldValue(pm, "eventPublisher"); - assertThat(eventPublisher).isNotNull(); - assertThat(eventPublisher instanceof DefaultAuthenticationEventPublisher).isTrue(); - pm.authenticate(UsernamePasswordAuthenticationToken.unauthenticated("bob", "bobspassword")); - assertThat(listener.events).hasSize(1); - } - - @Test - public void credentialsAreClearedByDefault() { - ConfigurableApplicationContext appContext = this.spring.context(CONTEXT).getContext(); - ProviderManager pm = (ProviderManager) appContext.getBeansOfType(ProviderManager.class).values().toArray()[0]; - assertThat(pm.isEraseCredentialsAfterAuthentication()).isTrue(); - } - - @Test - public void clearCredentialsPropertyIsRespected() { - ConfigurableApplicationContext appContext = this.spring - .context("") - .getContext(); - ProviderManager pm = (ProviderManager) appContext.getBeansOfType(ProviderManager.class).values().toArray()[0]; - assertThat(pm.isEraseCredentialsAfterAuthentication()).isFalse(); - } - - @Autowired - MockMvc mockMvc; - - @Test - public void passwordEncoderBeanUsed() throws Exception { - // @formatter:off - this.spring.context("" - + "" - + " " - + "" - + "" - + " " - + " " - + "") - .mockMvcAfterSpringSecurityOk() - .autowire(); - this.mockMvc.perform(get("/").with(httpBasic("user", "password"))) - .andExpect(status().isOk()); - // @formatter:on - } - - private static class AuthListener implements ApplicationListener { - - List events = new ArrayList<>(); - - @Override - public void onApplicationEvent(AbstractAuthenticationEvent event) { - this.events.add(event); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/authentication/AuthenticationProviderBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/authentication/AuthenticationProviderBeanDefinitionParserTests.java deleted file mode 100644 index 13f5c518f74..00000000000 --- a/config/src/test/java/org/springframework/security/config/authentication/AuthenticationProviderBeanDefinitionParserTests.java +++ /dev/null @@ -1,173 +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.authentication; - -import java.util.List; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.context.support.AbstractXmlApplicationContext; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.ProviderManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.BeanIds; -import org.springframework.security.config.util.InMemoryXmlApplicationContext; -import org.springframework.security.crypto.password.LdapShaPasswordEncoder; -import org.springframework.security.crypto.password.MessageDigestPasswordEncoder; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests for {@link AuthenticationProviderBeanDefinitionParser}. - * - * @author Luke Taylor - */ -public class AuthenticationProviderBeanDefinitionParserTests { - - private AbstractXmlApplicationContext appContext; - - private UsernamePasswordAuthenticationToken bob = UsernamePasswordAuthenticationToken.unauthenticated("bob", - "bobspassword"); - - @AfterEach - public void closeAppContext() { - if (this.appContext != null) { - this.appContext.close(); - } - } - - @Test - public void worksWithEmbeddedUserService() { - // @formatter:off - setContext(" " - + " " - + " " - + " " - + " "); - // @formatter:on - getProvider().authenticate(this.bob); - } - - @Test - public void externalUserServiceRefWorks() { - // @formatter:off - this.appContext = new InMemoryXmlApplicationContext( - " " - + " " - + " " + " " - + " " - + " "); - // @formatter:on - getProvider().authenticate(this.bob); - } - - @Test - public void providerWithBCryptPasswordEncoderWorks() { - // @formatter:off - setContext(" " - + " " - + " " - + " " - + " " - // @formatter:on - + " "); - getProvider().authenticate(this.bob); - } - - @Test - public void providerWithMd5PasswordEncoderWorks() { - // @formatter:off - this.appContext = new InMemoryXmlApplicationContext(" " - + " " - + " " - + " " - + " " - + " " - + " " - + " " - + " " - + " " - + " "); - // @formatter:on - getProvider().authenticate(this.bob); - } - - @Test - public void providerWithShaPasswordEncoderWorks() { - // @formatter:off - this.appContext = new InMemoryXmlApplicationContext(" " - + " " - + " " - + " " - + " " - + " " - + " " - + " " - + " "); - // @formatter:on - getProvider().authenticate(this.bob); - } - - @Test - public void passwordIsBase64EncodedWhenBase64IsEnabled() { - // @formatter:off - this.appContext = new InMemoryXmlApplicationContext(" " - + " " - + " " - + " " - + " " - + " " - + " " - + " " - + " " - + " " + " " - + " "); - // @formatter:on - getProvider().authenticate(this.bob); - } - - // SEC-1466 - @Test - public void exernalProviderDoesNotSupportChildElements() { - assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy(() -> - // @formatter:off - this.appContext = new InMemoryXmlApplicationContext(" " - + " " - + " " - + " " - + " " - + " " - + " ") - // @formatter:on - ); - } - - private AuthenticationProvider getProvider() { - List providers = ((ProviderManager) this.appContext - .getBean(BeanIds.AUTHENTICATION_MANAGER)).getProviders(); - return providers.get(0); - } - - private void setContext(String context) { - this.appContext = new InMemoryXmlApplicationContext( - "" + context + ""); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/authentication/JdbcUserServiceBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/authentication/JdbcUserServiceBeanDefinitionParserTests.java deleted file mode 100644 index f1523a58585..00000000000 --- a/config/src/test/java/org/springframework/security/config/authentication/JdbcUserServiceBeanDefinitionParserTests.java +++ /dev/null @@ -1,203 +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.authentication; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.w3c.dom.Element; -import org.xml.sax.SAXParseException; - -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.CachingUserDetailsService; -import org.springframework.security.authentication.ProviderManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.authentication.dao.DaoAuthenticationProvider; -import org.springframework.security.config.BeanIds; -import org.springframework.security.config.util.InMemoryXmlApplicationContext; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.provisioning.JdbcUserDetailsManager; -import org.springframework.security.util.FieldUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Mockito.mock; - -/** - * @author Ben Alex - * @author Luke Taylor - * @author Eddú Meléndez - */ -public class JdbcUserServiceBeanDefinitionParserTests { - - private static String USER_CACHE_XML = ""; - - // @formatter:off - private static String DATA_SOURCE = " " - + " " - + " " - + " " - + " " - + " "; - // @formatter:on - - private InMemoryXmlApplicationContext appContext; - - @AfterEach - public void closeAppContext() { - if (this.appContext != null) { - this.appContext.close(); - } - } - - @Test - public void beanNameIsCorrect() { - assertThat(JdbcUserDetailsManager.class.getName()) - .isEqualTo(new JdbcUserServiceBeanDefinitionParser().getBeanClassName(mock(Element.class))); - } - - @Test - public void validUsernameIsFound() { - setContext("" + DATA_SOURCE); - JdbcUserDetailsManager mgr = (JdbcUserDetailsManager) this.appContext.getBean(BeanIds.USER_DETAILS_SERVICE); - assertThat(mgr.loadUserByUsername("rod")).isNotNull(); - } - - @Test - public void beanIdIsParsedCorrectly() { - setContext("" + DATA_SOURCE); - assertThat(this.appContext.getBean("myUserService") instanceof JdbcUserDetailsManager).isTrue(); - } - - @Test - public void usernameAndAuthorityQueriesAreParsedCorrectly() throws Exception { - String userQuery = "select username, password, true from users where username = ?"; - String authoritiesQuery = "select username, authority from authorities where username = ? and 1 = 1"; - // @formatter:off - setContext("" + DATA_SOURCE); - // @formatter:on - JdbcUserDetailsManager mgr = (JdbcUserDetailsManager) this.appContext.getBean("myUserService"); - assertThat(FieldUtils.getFieldValue(mgr, "usersByUsernameQuery")).isEqualTo(userQuery); - assertThat(FieldUtils.getFieldValue(mgr, "authoritiesByUsernameQuery")).isEqualTo(authoritiesQuery); - assertThat(mgr.loadUserByUsername("rod") != null).isTrue(); - } - - @Test - public void groupQueryIsParsedCorrectly() throws Exception { - setContext("" + DATA_SOURCE); - JdbcUserDetailsManager mgr = (JdbcUserDetailsManager) this.appContext.getBean("myUserService"); - assertThat(FieldUtils.getFieldValue(mgr, "groupAuthoritiesByUsernameQuery")).isEqualTo("blah blah"); - assertThat((Boolean) FieldUtils.getFieldValue(mgr, "enableGroups")).isTrue(); - } - - @Test - public void cacheRefIsparsedCorrectly() { - setContext("" - + DATA_SOURCE + USER_CACHE_XML); - CachingUserDetailsService cachingUserService = (CachingUserDetailsService) this.appContext - .getBean("myUserService" + AbstractUserDetailsServiceBeanDefinitionParser.CACHING_SUFFIX); - assertThat(this.appContext.getBean("userCache")).isSameAs(cachingUserService.getUserCache()); - assertThat(cachingUserService.loadUserByUsername("rod")).isNotNull(); - assertThat(cachingUserService.loadUserByUsername("rod")).isNotNull(); - } - - @Test - public void isSupportedByAuthenticationProviderElement() { - // @formatter:off - setContext("" - + " " - + " " - + " " - + "" - + DATA_SOURCE); - // @formatter:on - AuthenticationManager mgr = (AuthenticationManager) this.appContext.getBean(BeanIds.AUTHENTICATION_MANAGER); - mgr.authenticate(UsernamePasswordAuthenticationToken.unauthenticated("rod", "koala")); - } - - @Test - public void cacheIsInjectedIntoAuthenticationProvider() { - // @formatter:off - setContext("" - + " " - + " " - + " " - + "" - + DATA_SOURCE - + USER_CACHE_XML); - // @formatter:on - ProviderManager mgr = (ProviderManager) this.appContext.getBean(BeanIds.AUTHENTICATION_MANAGER); - DaoAuthenticationProvider provider = (DaoAuthenticationProvider) mgr.getProviders().get(0); - assertThat(this.appContext.getBean("userCache")).isSameAs(provider.getUserCache()); - provider.authenticate(UsernamePasswordAuthenticationToken.unauthenticated("rod", "koala")); - assertThat(provider.getUserCache().getUserFromCache("rod")).isNotNull() - .withFailMessage("Cache should contain user after authentication"); - } - - @Test - public void rolePrefixIsUsedWhenSet() { - setContext("" - + DATA_SOURCE); - JdbcUserDetailsManager mgr = (JdbcUserDetailsManager) this.appContext.getBean("myUserService"); - UserDetails rod = mgr.loadUserByUsername("rod"); - assertThat(AuthorityUtils.authorityListToSet(rod.getAuthorities())).contains("PREFIX_ROLE_SUPERVISOR"); - } - - @Test - public void testEmptyDataSourceRef() { - // @formatter:off - String xml = "" - + " " - + " " - + " " - + ""; - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> setContext(xml)) - .withFailMessage("Expected exception due to empty data-source-ref") - .withMessageContaining("data-source-ref is required for jdbc-user-service"); - // @formatter:on - } - - @Test - public void testMissingDataSourceRef() { - // @formatter:off - String xml = "" - + " " - + " " - + " " - + ""; - assertThatExceptionOfType(XmlBeanDefinitionStoreException.class) - .isThrownBy(() -> setContext(xml)) - .withFailMessage("Expected exception due to missing data-source-ref") - .havingRootCause() - .isInstanceOf(SAXParseException.class) - .withMessageContaining("Attribute 'data-source-ref' must appear on element 'jdbc-user-service'"); - // @formatter:on - } - - private void setContext(String context) { - this.appContext = new InMemoryXmlApplicationContext(context); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/authentication/PasswordEncoderParserTests.java b/config/src/test/java/org/springframework/security/config/authentication/PasswordEncoderParserTests.java deleted file mode 100644 index 3f2ab1b8048..00000000000 --- a/config/src/test/java/org/springframework/security/config/authentication/PasswordEncoderParserTests.java +++ /dev/null @@ -1,82 +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.authentication; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.test.web.servlet.MockMvc; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Rob Winch - * @since 5.0 - */ -@ExtendWith(SpringTestContextExtension.class) -public class PasswordEncoderParserTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mockMvc; - - @Test - public void passwordEncoderDefaultsToDelegatingPasswordEncoder() throws Exception { - this.spring.configLocations( - "classpath:org/springframework/security/config/authentication/PasswordEncoderParserTests-default.xml") - .mockMvcAfterSpringSecurityOk() - .autowire(); - // @formatter:off - this.mockMvc.perform(get("/").with(httpBasic("user", "password"))) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void passwordEncoderDefaultsToPasswordEncoderBean() throws Exception { - this.spring - .configLocations( - "classpath:org/springframework/security/config/authentication/PasswordEncoderParserTests-bean.xml") - .mockMvcAfterSpringSecurityOk() - .autowire(); - // @formatter:off - this.mockMvc.perform(get("/").with(httpBasic("user", "password"))) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - void testCreatePasswordEncoderBeanDefinition() throws Exception { - String hash = "bcrypt"; - Class expectedBeanClass = BCryptPasswordEncoder.class; - - BeanDefinition beanDefinition = PasswordEncoderParser.createPasswordEncoderBeanDefinition(hash); - - Class actualBeanClass = Class.forName(beanDefinition.getBeanClassName()); - assertThat(actualBeanClass).isEqualTo(expectedBeanClass); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/authentication/UserServiceBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/authentication/UserServiceBeanDefinitionParserTests.java deleted file mode 100644 index db9146ee1e7..00000000000 --- a/config/src/test/java/org/springframework/security/config/authentication/UserServiceBeanDefinitionParserTests.java +++ /dev/null @@ -1,139 +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.authentication; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.FatalBeanException; -import org.springframework.context.support.AbstractXmlApplicationContext; -import org.springframework.security.config.util.InMemoryXmlApplicationContext; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.core.userdetails.UserDetailsService; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Luke Taylor - */ -public class UserServiceBeanDefinitionParserTests { - - private AbstractXmlApplicationContext appContext; - - @AfterEach - public void closeAppContext() { - if (this.appContext != null) { - this.appContext.close(); - } - } - - @Test - public void userServiceWithValidPropertiesFileWorksSuccessfully() { - setContext(""); - UserDetailsService userService = (UserDetailsService) this.appContext.getBean("service"); - userService.loadUserByUsername("bob"); - userService.loadUserByUsername("joe"); - } - - @Test - public void userServiceWithEmbeddedUsersWorksSuccessfully() { - // @formatter:off - setContext("" - + " " - + ""); - // @formatter:on - UserDetailsService userService = (UserDetailsService) this.appContext.getBean("service"); - userService.loadUserByUsername("joe"); - } - - @Test - public void namePasswordAndAuthoritiesSupportPlaceholders() { - System.setProperty("principal.name", "joe"); - System.setProperty("principal.pass", "joespassword"); - System.setProperty("principal.authorities", "ROLE_A,ROLE_B"); - // @formatter:off - setContext("" - + "" - + " " - + ""); - // @formatter:on - UserDetailsService userService = (UserDetailsService) this.appContext.getBean("service"); - UserDetails joe = userService.loadUserByUsername("joe"); - assertThat(joe.getPassword()).isEqualTo("joespassword"); - assertThat(joe.getAuthorities()).hasSize(2); - } - - @Test - public void embeddedUsersWithNoPasswordIsGivenGeneratedValue() { - // @formatter:off - setContext("" - + " " - + ""); - // @formatter:on - UserDetailsService userService = (UserDetailsService) this.appContext.getBean("service"); - UserDetails joe = userService.loadUserByUsername("joe"); - assertThat(joe.getPassword().length() > 0).isTrue(); - Long.parseLong(joe.getPassword()); - } - - @Test - public void disabledAndEmbeddedFlagsAreSupported() { - // @formatter:off - setContext("" - + " " - + " " - + ""); - // @formatter:on - UserDetailsService userService = (UserDetailsService) this.appContext.getBean("service"); - UserDetails joe = userService.loadUserByUsername("joe"); - assertThat(joe.isAccountNonLocked()).isFalse(); - // Check case-sensitive lookup SEC-1432 - UserDetails bob = userService.loadUserByUsername("Bob"); - assertThat(bob.isEnabled()).isFalse(); - } - - @Test - public void userWithBothPropertiesAndEmbeddedUsersThrowsException() { - assertThatExceptionOfType(FatalBeanException.class).isThrownBy(() -> - // @formatter:off - setContext("" - + " " - + "") - // @formatter:on - ); - } - - @Test - public void multipleTopLevelUseWithoutIdThrowsException() { - assertThatExceptionOfType(FatalBeanException.class).isThrownBy(() -> setContext( - "" - + "")); - } - - @Test - public void userServiceWithMissingPropertiesFileThrowsException() { - assertThatExceptionOfType(FatalBeanException.class) - .isThrownBy(() -> setContext("")); - } - - private void setContext(String context) { - this.appContext = new InMemoryXmlApplicationContext(context); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/core/GrantedAuthorityDefaultsJcTests.java b/config/src/test/java/org/springframework/security/config/core/GrantedAuthorityDefaultsJcTests.java deleted file mode 100644 index 356117b441e..00000000000 --- a/config/src/test/java/org/springframework/security/config/core/GrantedAuthorityDefaultsJcTests.java +++ /dev/null @@ -1,185 +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.core; - -import java.io.IOException; - -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -@ExtendWith(SpringExtension.class) -@ContextConfiguration -public class GrantedAuthorityDefaultsJcTests { - - @Autowired - FilterChainProxy springSecurityFilterChain; - - @Autowired - MessageService messageService; - - MockHttpServletRequest request; - - MockHttpServletResponse response; - - MockFilterChain chain; - - @BeforeEach - public void setup() { - setup("USER"); - this.request = new MockHttpServletRequest("GET", ""); - this.request.setMethod("GET"); - this.response = new MockHttpServletResponse(); - this.chain = new MockFilterChain(); - } - - @AfterEach - public void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test - public void doFilter() throws Exception { - SecurityContext context = SecurityContextHolder.getContext(); - this.request.getSession() - .setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - } - - @Test - public void doFilterDenied() throws Exception { - setup("DENIED"); - SecurityContext context = SecurityContextHolder.getContext(); - this.request.getSession() - .setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN); - } - - @Test - public void message() { - this.messageService.getMessage(); - } - - @Test - public void jsrMessage() { - this.messageService.getJsrMessage(); - } - - @Test - public void messageDenied() { - setup("DENIED"); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messageService::getMessage); - } - - @Test - public void jsrMessageDenied() { - setup("DENIED"); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messageService::getJsrMessage); - } - - // SEC-2926 - @Test - public void doFilterIsUserInRole() throws Exception { - SecurityContext context = SecurityContextHolder.getContext(); - this.request.getSession() - .setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context); - this.chain = new MockFilterChain() { - @Override - public void doFilter(ServletRequest request, ServletResponse response) - throws IOException, ServletException { - HttpServletRequest httpRequest = (HttpServletRequest) request; - assertThat(httpRequest.isUserInRole("USER")).isTrue(); - assertThat(httpRequest.isUserInRole("INVALID")).isFalse(); - super.doFilter(request, response); - } - }; - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.chain.getRequest()).isNotNull(); - } - - private void setup(String role) { - TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", role); - SecurityContextHolder.getContext().setAuthentication(user); - } - - @Configuration - @EnableWebSecurity - @EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true) - static class Config { - - @Autowired - void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { - // @formatter:off - auth - .inMemoryAuthentication() - .withUser("user").password("password").roles("USER"); - // @formatter:on - } - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests.anyRequest().hasRole("USER")); - return http.build(); - // @formatter:on - } - - @Bean - MessageService messageService() { - return new HelloWorldMessageService(); - } - - @Bean - static GrantedAuthorityDefaults grantedAuthorityDefaults() { - return new GrantedAuthorityDefaults(""); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/core/GrantedAuthorityDefaultsXmlTests.java b/config/src/test/java/org/springframework/security/config/core/GrantedAuthorityDefaultsXmlTests.java deleted file mode 100644 index 56b829e4557..00000000000 --- a/config/src/test/java/org/springframework/security/config/core/GrantedAuthorityDefaultsXmlTests.java +++ /dev/null @@ -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.core; - -import java.io.IOException; - -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -@ExtendWith(SpringExtension.class) -@ContextConfiguration -public class GrantedAuthorityDefaultsXmlTests { - - @Autowired - FilterChainProxy springSecurityFilterChain; - - @Autowired - MessageService messageService; - - MockHttpServletRequest request; - - MockHttpServletResponse response; - - MockFilterChain chain; - - @BeforeEach - public void setup() { - setup("USER"); - this.request = new MockHttpServletRequest("GET", ""); - this.request.setMethod("GET"); - this.response = new MockHttpServletResponse(); - this.chain = new MockFilterChain(); - } - - @AfterEach - public void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test - public void doFilter() throws Exception { - SecurityContext context = SecurityContextHolder.getContext(); - this.request.getSession() - .setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - } - - @Test - public void doFilterDenied() throws Exception { - setup("DENIED"); - SecurityContext context = SecurityContextHolder.getContext(); - this.request.getSession() - .setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN); - } - - @Test - public void message() { - this.messageService.getMessage(); - } - - @Test - public void jsrMessage() { - this.messageService.getJsrMessage(); - } - - @Test - public void messageDenied() { - setup("DENIED"); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messageService::getMessage); - } - - @Test - public void jsrMessageDenied() { - setup("DENIED"); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.messageService::getJsrMessage); - } - - // SEC-2926 - @Test - public void doFilterIsUserInRole() throws Exception { - SecurityContext context = SecurityContextHolder.getContext(); - this.request.getSession() - .setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context); - this.chain = new MockFilterChain() { - @Override - public void doFilter(ServletRequest request, ServletResponse response) - throws IOException, ServletException { - HttpServletRequest httpRequest = (HttpServletRequest) request; - assertThat(httpRequest.isUserInRole("USER")).isTrue(); - assertThat(httpRequest.isUserInRole("INVALID")).isFalse(); - super.doFilter(request, response); - } - }; - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.chain.getRequest()).isNotNull(); - } - - private void setup(String role) { - TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password", role); - SecurityContextHolder.getContext().setAuthentication(user); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/core/HelloWorldMessageService.java b/config/src/test/java/org/springframework/security/config/core/HelloWorldMessageService.java deleted file mode 100755 index 22d10a33c04..00000000000 --- a/config/src/test/java/org/springframework/security/config/core/HelloWorldMessageService.java +++ /dev/null @@ -1,40 +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.core; - -import jakarta.annotation.security.RolesAllowed; - -import org.springframework.security.access.prepost.PreAuthorize; - -/** - * @author Rob Winch - */ -public class HelloWorldMessageService implements MessageService { - - @Override - @PreAuthorize("hasRole('USER')") - public String getMessage() { - return "Hello World"; - } - - @Override - @RolesAllowed("USER") - public String getJsrMessage() { - return "Hello JSR"; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/core/MessageService.java b/config/src/test/java/org/springframework/security/config/core/MessageService.java deleted file mode 100755 index d05c744b58e..00000000000 --- a/config/src/test/java/org/springframework/security/config/core/MessageService.java +++ /dev/null @@ -1,28 +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.core; - -/** - * @author Rob Winch - */ -public interface MessageService { - - String getMessage(); - - String getJsrMessage(); - -} diff --git a/config/src/test/java/org/springframework/security/config/core/userdetails/ReactiveUserDetailsServiceResourceFactoryBeanPropertiesResourceITests.java b/config/src/test/java/org/springframework/security/config/core/userdetails/ReactiveUserDetailsServiceResourceFactoryBeanPropertiesResourceITests.java deleted file mode 100644 index 61cb21a5d06..00000000000 --- a/config/src/test/java/org/springframework/security/config/core/userdetails/ReactiveUserDetailsServiceResourceFactoryBeanPropertiesResourceITests.java +++ /dev/null @@ -1,57 +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.core.userdetails; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.core.userdetails.ReactiveUserDetailsService; -import org.springframework.security.util.InMemoryResource; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Rob Winch - * @since 5.0 - */ -@ExtendWith(SpringExtension.class) -public class ReactiveUserDetailsServiceResourceFactoryBeanPropertiesResourceITests { - - @Autowired - ReactiveUserDetailsService users; - - @Test - public void loadUserByUsernameWhenUserFoundThenNotNull() { - assertThat(this.users.findByUsername("user").block()).isNotNull(); - } - - @Configuration - static class Config { - - @Bean - ReactiveUserDetailsServiceResourceFactoryBean userDetailsService() { - return ReactiveUserDetailsServiceResourceFactoryBean - .fromResource(new InMemoryResource("user=password,ROLE_USER")); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/core/userdetails/ReactiveUserDetailsServiceResourceFactoryBeanPropertiesResourceLocationITests.java b/config/src/test/java/org/springframework/security/config/core/userdetails/ReactiveUserDetailsServiceResourceFactoryBeanPropertiesResourceLocationITests.java deleted file mode 100644 index c16b9991523..00000000000 --- a/config/src/test/java/org/springframework/security/config/core/userdetails/ReactiveUserDetailsServiceResourceFactoryBeanPropertiesResourceLocationITests.java +++ /dev/null @@ -1,55 +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.core.userdetails; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.core.userdetails.ReactiveUserDetailsService; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Rob Winch - * @since 5.0 - */ -@ExtendWith(SpringExtension.class) -public class ReactiveUserDetailsServiceResourceFactoryBeanPropertiesResourceLocationITests { - - @Autowired - ReactiveUserDetailsService users; - - @Test - public void loadUserByUsernameWhenUserFoundThenNotNull() { - assertThat(this.users.findByUsername("user").block()).isNotNull(); - } - - @Configuration - static class Config { - - @Bean - ReactiveUserDetailsServiceResourceFactoryBean userDetailsService() { - return ReactiveUserDetailsServiceResourceFactoryBean.fromResourceLocation("classpath:users.properties"); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/core/userdetails/ReactiveUserDetailsServiceResourceFactoryBeanStringITests.java b/config/src/test/java/org/springframework/security/config/core/userdetails/ReactiveUserDetailsServiceResourceFactoryBeanStringITests.java deleted file mode 100644 index fe32ba1ea7b..00000000000 --- a/config/src/test/java/org/springframework/security/config/core/userdetails/ReactiveUserDetailsServiceResourceFactoryBeanStringITests.java +++ /dev/null @@ -1,55 +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.core.userdetails; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.core.userdetails.ReactiveUserDetailsService; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Rob Winch - * @since 5.0 - */ -@ExtendWith(SpringExtension.class) -public class ReactiveUserDetailsServiceResourceFactoryBeanStringITests { - - @Autowired - ReactiveUserDetailsService users; - - @Test - public void findByUsernameWhenUserFoundThenNotNull() { - assertThat(this.users.findByUsername("user").block()).isNotNull(); - } - - @Configuration - static class Config { - - @Bean - ReactiveUserDetailsServiceResourceFactoryBean userDetailsService() { - return ReactiveUserDetailsServiceResourceFactoryBean.fromString("user=password,ROLE_USER"); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/core/userdetails/UserDetailsResourceFactoryBeanTests.java b/config/src/test/java/org/springframework/security/config/core/userdetails/UserDetailsResourceFactoryBeanTests.java deleted file mode 100644 index 1daed18c48f..00000000000 --- a/config/src/test/java/org/springframework/security/config/core/userdetails/UserDetailsResourceFactoryBeanTests.java +++ /dev/null @@ -1,107 +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.core.userdetails; - -import java.util.Collection; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.core.io.ResourceLoader; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.security.util.InMemoryResource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; - -/** - * @author Rob Winch - * @since 5.0 - */ -@ExtendWith(MockitoExtension.class) -public class UserDetailsResourceFactoryBeanTests { - - @Mock - ResourceLoader resourceLoader; - - UserDetailsResourceFactoryBean factory = new UserDetailsResourceFactoryBean(); - - @Test - public void setResourceLoaderWhenNullThenThrowsException() { - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.factory.setResourceLoader(null)) - .withStackTraceContaining("resourceLoader cannot be null"); - // @formatter:on - } - - @Test - public void getObjectWhenPropertiesResourceLocationNullThenThrowsIllegalStateException() { - this.factory.setResourceLoader(this.resourceLoader); - // @formatter:off - assertThatIllegalArgumentException() - .isThrownBy(() -> this.factory.getObject()) - .withStackTraceContaining("resource cannot be null if resourceLocation is null"); - // @formatter:on - } - - @Test - public void getObjectWhenPropertiesResourceLocationSingleUserThenThrowsGetsSingleUser() throws Exception { - this.factory.setResourceLocation("classpath:users.properties"); - Collection users = this.factory.getObject(); - assertLoaded(); - } - - @Test - public void getObjectWhenPropertiesResourceSingleUserThenThrowsGetsSingleUser() throws Exception { - this.factory.setResource(new InMemoryResource("user=password,ROLE_USER")); - assertLoaded(); - } - - @Test - public void getObjectWhenInvalidUserThenThrowsMeaningfulException() { - this.factory.setResource(new InMemoryResource("user=invalidFormatHere")); - // @formatter:off - assertThatIllegalStateException() - .isThrownBy(() -> this.factory.getObject()) - .withStackTraceContaining("user") - .withStackTraceContaining("invalidFormatHere"); - // @formatter:on - } - - @Test - public void getObjectWhenStringSingleUserThenGetsSingleUser() throws Exception { - this.factory = UserDetailsResourceFactoryBean.fromString("user=password,ROLE_USER"); - assertLoaded(); - } - - private void assertLoaded() throws Exception { - Collection users = this.factory.getObject(); - // @formatter:off - UserDetails expectedUser = User.withUsername("user") - .password("password") - .authorities("ROLE_USER") - .build(); - // @formatter:on - assertThat(users).containsExactly(expectedUser); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/crypto/RsaKeyConversionServicePostProcessorTests.java b/config/src/test/java/org/springframework/security/config/crypto/RsaKeyConversionServicePostProcessorTests.java deleted file mode 100644 index 2c1326fd502..00000000000 --- a/config/src/test/java/org/springframework/security/config/crypto/RsaKeyConversionServicePostProcessorTests.java +++ /dev/null @@ -1,199 +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.crypto; - -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.beans.factory.config.BeanFactoryPostProcessor; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.support.GenericConversionService; -import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests for {@link RsaKeyConversionServicePostProcessor} - */ -@ExtendWith(SpringTestContextExtension.class) -public class RsaKeyConversionServicePostProcessorTests { - - // @formatter:off - private static final String PKCS8_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----\n" - + "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCMk7CKSTfu3QoV\n" - + "HoPVXxwZO+qweztd36cVWYqGOZinrOR2crWFu50AgR2CsdIH0+cqo7F4Vx7/3O8i\n" - + "RpYYZPe2VoO5sumzJt8P6fS80/TAKjhJDAqgZKRJTgGN8KxCM6p/aJli1ZeDBqiV\n" - + "v7vJJe+ZgJuPGRS+HMNa/wPxEkqqXsglcJcQV1ZEtfKXSHB7jizKpRL38185SyAC\n" - + "pwyjvBu6Cmm1URfhQo88mf239ONh4dZ2HoDfzN1q6Ssu4F4hgutxr9B0DVLDP5u+\n" - + "WFrm3nsJ76zf99uJ+ntMUHJ+bY+gOjSlVWIVBIZeAaEGKCNWRk/knjvjbijpvm3U\n" - + "acGlgdL3AgMBAAECggEACxxxS7zVyu91qI2s5eSKmAQAXMqgup6+2hUluc47nqUv\n" - + "uZz/c/6MPkn2Ryo+65d4IgqmMFjSfm68B/2ER5FTcvoLl1Xo2twrrVpUmcg3BClS\n" - + "IZPuExdhVNnxjYKEWwcyZrehyAoR261fDdcFxLRW588efIUC+rPTTRHzAc7sT+Ln\n" - + "t/uFeYNWJm3LaegOLoOmlMAhJ5puAWSN1F0FxtRf/RVgzbLA9QC975SKHJsfWCSr\n" - + "IZyPsdeaqomKaF65l8nfqlE0Ua2L35gIOGKjUwb7uUE8nI362RWMtYdoi3zDDyoY\n" - + "hSFbgjylCHDM0u6iSh6KfqOHtkYyJ8tUYgVWl787wQKBgQDYO3wL7xuDdD101Lyl\n" - + "AnaDdFB9fxp83FG1cWr+t7LYm9YxGfEUsKHAJXN6TIayDkOOoVwIl+Gz0T3Z06Bm\n" - + "eBGLrB9mrVA7+C7NJwu5gTMlzP6HxUR9zKJIQ/VB1NUGM77LSmvOFbHc9Q0+z8EH\n" - + "X5WO516a3Z7lNtZJcCoPOtu2rwKBgQCmbj41Fh+SSEUApCEKms5ETRpe7LXQlJgx\n" - + "yW7zcJNNuIb1C3vBLPxjiOTMgYKOeMg5rtHTGLT43URHLh9ArjawasjSAr4AM3J4\n" - + "xpoi/sKGDdiKOsuDWIGfzdYL8qyTHSdpZLQsCTMRiRYgAHZFPgNa7SLZRfZicGlr\n" - + "GHN1rJW6OQKBgEjiM/upyrJSWeypUDSmUeAZMpA6aWkwsfHgmtnkfUn5rQa74cDB\n" - + "kKO9e+D7LmOR3z+SL/1NhGwh2SE07dncGr3jdGodfO/ZxZyszozmeaECKcEFwwJM\n" - + "GV8WWPKplGwUwPiwywmZ0mvRxXcoe73KgBS88+xrSwWjqDL0tZiQlEJNAoGATkei\n" - + "GMQMG3jEg9Wu+NbxV6zQT3+U0MNjhl9RQU1c63x0dcNt9OFc4NAdlZcAulRTENaK\n" - + "OHjxffBM0hH+fySx8m53gFfr2BpaqDX5f6ZGBlly1SlsWZ4CchCVsc71nshipi7I\n" - + "k8HL9F5/OpQdDNprJ5RMBNfkWE65Nrcsb1e6oPkCgYAxwgdiSOtNg8PjDVDmAhwT\n" - + "Mxj0Dtwi2fAqQ76RVrrXpNp3uCOIAu4CfruIb5llcJ3uak0ZbnWri32AxSgk80y3\n" - + "EWiRX/WEDu5znejF+5O3pI02atWWcnxifEKGGlxwkcMbQdA67MlrJLFaSnnGpNXo\n" - + "yPfcul058SOqhafIZQMEKQ==\n" - + "-----END PRIVATE KEY-----"; - // @formatter:on - - private static final String X509_PUBLIC_KEY_LOCATION = "classpath:org/springframework/security/config/annotation/web/configuration/simple.pub"; - - private final RsaKeyConversionServicePostProcessor postProcessor = new RsaKeyConversionServicePostProcessor(); - - private ConversionService service; - - @Value("classpath:org/springframework/security/config/annotation/web/configuration/simple.pub") - RSAPublicKey publicKey; - - @Value("classpath:org/springframework/security/config/annotation/web/configuration/simple.priv") - RSAPrivateKey privateKey; - - @Value("custom:simple.pub") - RSAPublicKey samePublicKey; - - public final SpringTestContext spring = new SpringTestContext(this); - - @BeforeEach - public void setUp() { - ConfigurableListableBeanFactory beanFactory = new DefaultListableBeanFactory(); - beanFactory.setConversionService(new GenericConversionService()); - this.postProcessor.postProcessBeanFactory(beanFactory); - this.service = beanFactory.getConversionService(); - } - - @Test - public void convertWhenUsingConversionServiceForRawKeyThenOk() { - RSAPrivateKey key = this.service.convert(PKCS8_PRIVATE_KEY, RSAPrivateKey.class); - assertThat(key.getModulus().bitLength()).isEqualTo(2048); - } - - @Test - public void convertWhenUsingConversionServiceForClasspathThenOk() { - RSAPublicKey key = this.service.convert(X509_PUBLIC_KEY_LOCATION, RSAPublicKey.class); - assertThat(key.getModulus().bitLength()).isEqualTo(1024); - } - - @Test - public void valueWhenReferringToClasspathPublicKeyThenConverts() { - this.spring.register(CustomResourceLoaderConfig.class, DefaultConfig.class).autowire(); - assertThat(this.publicKey.getModulus().bitLength()).isEqualTo(1024); - } - - @Test - public void valueWhenReferringToClasspathPrivateKeyThenConverts() { - this.spring.register(CustomResourceLoaderConfig.class, DefaultConfig.class).autowire(); - assertThat(this.privateKey.getModulus().bitLength()).isEqualTo(2048); - } - - @Test - public void valueWhenReferringToCustomResourceLoadedPublicKeyThenConverts() { - this.spring.register(CustomResourceLoaderConfig.class, DefaultConfig.class).autowire(); - assertThat(this.samePublicKey.getModulus().bitLength()).isEqualTo(1024); - } - - @Test - public void valueWhenOverridingConversionServiceThenUsed() { - assertThatExceptionOfType(Exception.class) - .isThrownBy( - () -> this.spring.register(OverrideConversionServiceConfig.class, DefaultConfig.class).autowire()) - .withRootCauseInstanceOf(IllegalArgumentException.class); - } - - @Configuration - @EnableWebSecurity - static class DefaultConfig { - - } - - @Configuration - static class CustomResourceLoaderConfig { - - @Bean - BeanFactoryPostProcessor conversionServiceCustomizer() { - return (beanFactory) -> beanFactory.getBean(RsaKeyConversionServicePostProcessor.class) - .setResourceLoader(new CustomResourceLoader()); - } - - } - - @Configuration - static class OverrideConversionServiceConfig { - - @Bean - ConversionService conversionService() { - GenericConversionService service = new GenericConversionService(); - service.addConverter(String.class, RSAPublicKey.class, (source) -> { - throw new IllegalArgumentException("unsupported"); - }); - return service; - } - - } - - private static class CustomResourceLoader implements ResourceLoader { - - private final ResourceLoader delegate = new DefaultResourceLoader(); - - @Override - public Resource getResource(String location) { - if (location.startsWith("classpath:")) { - return this.delegate.getResource(location); - } - else if (location.startsWith("custom:")) { - String[] parts = location.split(":"); - return this.delegate.getResource( - "classpath:org/springframework/security/config/annotation/web/configuration/" + parts[1]); - } - throw new IllegalArgumentException("unsupported resource"); - } - - @Override - public ClassLoader getClassLoader() { - return this.delegate.getClassLoader(); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/debug/AuthProviderDependency.java b/config/src/test/java/org/springframework/security/config/debug/AuthProviderDependency.java deleted file mode 100644 index 2ff3716d3eb..00000000000 --- a/config/src/test/java/org/springframework/security/config/debug/AuthProviderDependency.java +++ /dev/null @@ -1,30 +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.debug; - -import org.springframework.stereotype.Component; - -/** - * Fake depenency for {@link TestAuthenticationProvider} - * - * @author Rob Winch - * - */ -@Component -public class AuthProviderDependency { - -} diff --git a/config/src/test/java/org/springframework/security/config/debug/SecurityDebugBeanFactoryPostProcessorTests.java b/config/src/test/java/org/springframework/security/config/debug/SecurityDebugBeanFactoryPostProcessorTests.java deleted file mode 100644 index 8f5df2170ee..00000000000 --- a/config/src/test/java/org/springframework/security/config/debug/SecurityDebugBeanFactoryPostProcessorTests.java +++ /dev/null @@ -1,50 +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.debug; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.security.config.BeanIds; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.debug.DebugFilter; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Rob Winch - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class SecurityDebugBeanFactoryPostProcessorTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - public void contextRefreshWhenInDebugModeAndDependencyHasAutowiredConstructorThenDebugModeStillWorks() { - // SEC-1885 - this.spring.configLocations( - "classpath:org/springframework/security/config/debug/SecurityDebugBeanFactoryPostProcessorTests-context.xml") - .autowire(); - assertThat(this.spring.getContext().getBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN)) - .isInstanceOf(DebugFilter.class); - assertThat(this.spring.getContext().getBean(BeanIds.FILTER_CHAIN_PROXY)).isInstanceOf(FilterChainProxy.class); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/debug/TestAuthenticationProvider.java b/config/src/test/java/org/springframework/security/config/debug/TestAuthenticationProvider.java deleted file mode 100644 index eb77ed8c129..00000000000 --- a/config/src/test/java/org/springframework/security/config/debug/TestAuthenticationProvider.java +++ /dev/null @@ -1,49 +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.debug; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.stereotype.Service; - -/** - * An {@link AuthenticationProvider} that has an {@link Autowired} constructor which is - * necessary to recreate SEC-1885. - * - * @author Rob Winch - * - */ -@Service("authProvider") -public class TestAuthenticationProvider implements AuthenticationProvider { - - @Autowired - public TestAuthenticationProvider(AuthProviderDependency authProviderDependency) { - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - throw new UnsupportedOperationException(); - } - - @Override - public boolean supports(Class authentication) { - throw new UnsupportedOperationException(); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/doc/Attribute.java b/config/src/test/java/org/springframework/security/config/doc/Attribute.java deleted file mode 100644 index 4317438ac7d..00000000000 --- a/config/src/test/java/org/springframework/security/config/doc/Attribute.java +++ /dev/null @@ -1,69 +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.doc; - -/** - * Represents a Spring Security XSD Attribute. It is created when parsing the current xsd - * to compare to the documented appendix. - * - * @author Rob Winch - * @author Josh Cummings - * @see SpringSecurityXsdParser - * @see XsdDocumentedTests - */ -public class Attribute { - - private String name; - - private String desc; - - private Element elmt; - - public Attribute(String desc, String name) { - this.desc = desc; - this.name = name; - } - - public String getName() { - return this.name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDesc() { - return this.desc; - } - - public void setDesc(String desc) { - this.desc = desc; - } - - public Element getElmt() { - return this.elmt; - } - - public void setElmt(Element elmt) { - this.elmt = elmt; - } - - public String getId() { - return String.format("%s-%s", this.elmt.getId(), this.name); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/doc/Element.java b/config/src/test/java/org/springframework/security/config/doc/Element.java deleted file mode 100644 index 6eed0bec21d..00000000000 --- a/config/src/test/java/org/springframework/security/config/doc/Element.java +++ /dev/null @@ -1,162 +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.doc; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -/** - * Represents a Spring Security XSD Element. It is created when parsing the current xsd to - * compare to the documented appendix. - * - * @author Rob Winch - * @author Josh Cummings - * @see SpringSecurityXsdParser - * @see XsdDocumentedTests - */ -public class Element { - - private String name; - - private String desc; - - private Collection attrs = new ArrayList<>(); - - /** - * Contains the elements that extend this element (i.e. any-user-service contains - * ldap-user-service) - */ - private Collection subGrps = new ArrayList<>(); - - private Map childElmts = new HashMap<>(); - - private Map parentElmts = new HashMap<>(); - - public String getId() { - return String.format("nsa-%s", this.name); - } - - public String getName() { - return this.name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDesc() { - return this.desc; - } - - public void setDesc(String desc) { - this.desc = desc; - } - - public Collection getAttrs() { - return this.attrs; - } - - public void setAttrs(Collection attrs) { - this.attrs = attrs; - } - - public Collection getSubGrps() { - return this.subGrps; - } - - public void setSubGrps(Collection subGrps) { - this.subGrps = subGrps; - } - - public Map getChildElmts() { - return this.childElmts; - } - - public void setChildElmts(Map childElmts) { - this.childElmts = childElmts; - } - - public Map getParentElmts() { - return this.parentElmts; - } - - public void setParentElmts(Map parentElmts) { - this.parentElmts = parentElmts; - } - - /** - * Gets all the ids related to this Element including attributes, parent elements, and - * child elements. - * - *

- * The expected ids to be found are documented below. - *

    - *
  • Elements - any xml element will have the nsa-<element>. For example the - * http element will have the id nsa-http
  • - *
  • Parent Section - Any element with a parent other than beans will have a section - * named nsa-<element>-parents. For example, authentication-provider would have - * a section id of nsa-authentication-provider-parents. The section would then contain - * a list of links pointing to the documentation for each parent element.
  • - *
  • Attributes Section - Any element with attributes will have a section with the - * id nsa-<element>-attributes. For example the http element would require a - * section with the id http-attributes.
  • - *
  • Attribute - Each attribute of an element would have an id of - * nsa-<element>-<attributeName>. For example the attribute create-session - * for the http attribute would have the id http-create-session.
  • - *
  • Child Section - Any element with a child element will have a section named - * nsa-<element>-children. For example, authentication-provider would have a - * section id of nsa-authentication-provider-children. The section would then contain - * a list of links pointing to the documentation for each child element.
  • - *
- * @return - */ - public Collection getIds() { - Collection ids = new ArrayList<>(); - ids.add(getId()); - this.childElmts.values().forEach((elmt) -> ids.add(elmt.getId())); - this.attrs.forEach((attr) -> ids.add(attr.getId())); - if (!this.childElmts.isEmpty()) { - ids.add(getId() + "-children"); - } - if (!this.attrs.isEmpty()) { - ids.add(getId() + "-attributes"); - } - if (!this.parentElmts.isEmpty()) { - ids.add(getId() + "-parents"); - } - return ids; - } - - public Map getAllChildElmts() { - Map result = new HashMap<>(); - this.childElmts.values() - .forEach((elmt) -> elmt.subGrps.forEach((subElmt) -> result.put(subElmt.name, subElmt))); - result.putAll(this.childElmts); - return result; - } - - public Map getAllParentElmts() { - Map result = new HashMap<>(); - this.parentElmts.values() - .forEach((elmt) -> elmt.subGrps.forEach((subElmt) -> result.put(subElmt.name, subElmt))); - result.putAll(this.parentElmts); - return result; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/doc/SpringSecurityXsdParser.java b/config/src/test/java/org/springframework/security/config/doc/SpringSecurityXsdParser.java deleted file mode 100644 index 6428600792e..00000000000 --- a/config/src/test/java/org/springframework/security/config/doc/SpringSecurityXsdParser.java +++ /dev/null @@ -1,212 +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.doc; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; -import java.util.stream.Stream; - -import org.springframework.util.StringUtils; - -/** - * Parses the Spring Security Xsd Document - * - * @author Rob Winch - * @author Josh Cummings - */ -public class SpringSecurityXsdParser { - - private XmlNode rootElement; - - private Set attrElmts = new LinkedHashSet<>(); - - private Map elementNameToElement = new HashMap<>(); - - public SpringSecurityXsdParser(XmlNode rootElement) { - this.rootElement = rootElement; - } - - /** - * Returns a map of the element name to the {@link Element}. - * @return - */ - public Map parse() { - elements(this.rootElement); - return this.elementNameToElement; - } - - /** - * Creates a Map of the name to an Element object of all the children of element. - * @param node - * @return - */ - private Map elements(XmlNode node) { - Map elementNameToElement = new HashMap<>(); - node.children().forEach((child) -> { - if ("element".equals(child.simpleName())) { - Element e = elmt(child); - elementNameToElement.put(e.getName(), e); - } - else { - elementNameToElement.putAll(elements(child)); - } - }); - return elementNameToElement; - } - - /** - * Any children that are attribute will be returned as an Attribute object. - * @param element - * @return a collection of Attribute objects that are children of element. - */ - private Collection attrs(XmlNode element) { - Collection attrs = new ArrayList<>(); - element.children().forEach((c) -> { - String name = c.simpleName(); - if ("attribute".equals(name)) { - attrs.add(attr(c)); - } - else if (!"element".equals(name)) { - attrs.addAll(attrs(c)); - } - }); - return attrs; - } - - /** - * Any children will be searched for an attributeGroup, each of its children will be - * returned as an Attribute - * @param element - * @return - */ - private Collection attrgrps(XmlNode element) { - Collection attrgrp = new ArrayList<>(); - element.children().forEach((c) -> { - if (!"element".equals(c.simpleName())) { - if ("attributeGroup".equals(c.simpleName())) { - if (c.attribute("name") != null) { - attrgrp.addAll(attrgrp(c)); - } - else { - String name = c.attribute("ref").split(":")[1]; - XmlNode attrGrp = findNode(element, name); - attrgrp.addAll(attrgrp(attrGrp)); - } - } - else { - attrgrp.addAll(attrgrps(c)); - } - } - }); - return attrgrp; - } - - private XmlNode findNode(XmlNode c, String name) { - XmlNode root = c; - while (!"schema".equals(root.simpleName())) { - root = root.parent().get(); - } - // @formatter:off - return expand(root) - .filter((node) -> name.equals(node.attribute("name"))) - .findFirst() - .orElseThrow(IllegalArgumentException::new); - // @formatter:on - } - - private Stream expand(XmlNode root) { - // @formatter:off - return Stream.concat(Stream.of(root), root.children() - .flatMap(this::expand)); - // @formatter:on - } - - /** - * Processes an individual attributeGroup by obtaining all the attributes and then - * looking for more attributeGroup elements and prcessing them. - * @param e - * @return all the attributes for a specific attributeGroup and any child - * attributeGroups - */ - private Collection attrgrp(XmlNode e) { - Collection attrs = attrs(e); - attrs.addAll(attrgrps(e)); - return attrs; - } - - /** - * Obtains the description for a specific element - * @param element - * @return - */ - private String desc(XmlNode element) { - return element.child("annotation") - .flatMap((annotation) -> annotation.child("documentation")) - .map((documentation) -> documentation.text()) - .orElse(null); - } - - /** - * Given an element creates an attribute from it. - * @param n - * @return - */ - private Attribute attr(XmlNode n) { - return new Attribute(desc(n), n.attribute("name")); - } - - /** - * Given an element creates an Element out of it by collecting all its attributes and - * child elements. - * @param n - * @return - */ - private Element elmt(XmlNode n) { - String name = n.attribute("ref"); - if (!StringUtils.hasLength(name)) { - name = n.attribute("name"); - } - else { - name = name.split(":")[1]; - n = findNode(n, name); - } - if (this.elementNameToElement.containsKey(name)) { - return this.elementNameToElement.get(name); - } - this.attrElmts.add(name); - Element e = new Element(); - e.setName(n.attribute("name")); - e.setDesc(desc(n)); - e.setChildElmts(elements(n)); - e.setAttrs(attrs(n)); - e.getAttrs().addAll(attrgrps(n)); - e.getAttrs().forEach((attr) -> attr.setElmt(e)); - e.getChildElmts().values().forEach((element) -> element.getParentElmts().put(e.getName(), e)); - String subGrpName = n.attribute("substitutionGroup"); - if (StringUtils.hasLength(subGrpName)) { - Element subGrp = elmt(findNode(n, subGrpName.split(":")[1])); - subGrp.getSubGrps().add(e); - } - this.elementNameToElement.put(name, e); - return e; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/doc/XmlNode.java b/config/src/test/java/org/springframework/security/config/doc/XmlNode.java deleted file mode 100644 index b81aa1c4362..00000000000 --- a/config/src/test/java/org/springframework/security/config/doc/XmlNode.java +++ /dev/null @@ -1,78 +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.doc; - -import java.util.Optional; -import java.util.stream.IntStream; -import java.util.stream.Stream; - -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; - -/** - * @author Josh Cummings - */ -public class XmlNode { - - private final Node node; - - public XmlNode(Node node) { - this.node = node; - } - - public String simpleName() { - String[] parts = this.node.getNodeName().split(":"); - return parts[parts.length - 1]; - } - - public String text() { - return this.node.getTextContent(); - } - - public Stream children() { - NodeList children = this.node.getChildNodes(); - // @formatter:off - return IntStream.range(0, children.getLength()) - .mapToObj(children::item) - .map(XmlNode::new); - // @formatter:on - } - - public Optional child(String name) { - return this.children().filter((child) -> name.equals(child.simpleName())).findFirst(); - } - - public Optional parent() { - // @formatter:off - return Optional.ofNullable(this.node.getParentNode()).map(XmlNode::new); - // @formatter:on - } - - public String attribute(String name) { - // @formatter:off - return Optional.ofNullable(this.node.getAttributes()) - .map((attrs) -> attrs.getNamedItem(name)) - .map(Node::getTextContent) - .orElse(null); - // @formatter:on - } - - public Node node() { - return this.node; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/doc/XmlParser.java b/config/src/test/java/org/springframework/security/config/doc/XmlParser.java deleted file mode 100644 index 1c925d70371..00000000000 --- a/config/src/test/java/org/springframework/security/config/doc/XmlParser.java +++ /dev/null @@ -1,55 +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.doc; - -import java.io.IOException; -import java.io.InputStream; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import org.xml.sax.SAXException; - -/** - * @author Josh Cummings - */ -public class XmlParser implements AutoCloseable { - - private InputStream xml; - - public XmlParser(InputStream xml) { - this.xml = xml; - } - - public XmlNode parse() { - try { - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); - return new XmlNode(dBuilder.parse(this.xml)); - } - catch (IOException | ParserConfigurationException | SAXException ex) { - throw new IllegalStateException(ex); - } - } - - @Override - public void close() throws IOException { - this.xml.close(); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/doc/XmlSupport.java b/config/src/test/java/org/springframework/security/config/doc/XmlSupport.java deleted file mode 100644 index 790fe56bac4..00000000000 --- a/config/src/test/java/org/springframework/security/config/doc/XmlSupport.java +++ /dev/null @@ -1,50 +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.doc; - -import java.io.IOException; -import java.util.Map; - -import org.springframework.core.io.ClassPathResource; - -/** - * Support for ensuring preparing the givens in {@link XsdDocumentedTests} - * - * @author Josh Cummings - */ -public class XmlSupport { - - private XmlParser parser; - - public XmlNode parse(String location) throws IOException { - ClassPathResource resource = new ClassPathResource(location); - this.parser = new XmlParser(resource.getInputStream()); - return this.parser.parse(); - } - - public Map elementsByElementName(String location) throws IOException { - XmlNode node = parse(location); - return new SpringSecurityXsdParser(node).parse(); - } - - public void close() throws IOException { - if (this.parser != null) { - this.parser.close(); - } - } - -} diff --git a/config/src/test/java/org/springframework/security/config/doc/XsdDocumentedTests.java b/config/src/test/java/org/springframework/security/config/doc/XsdDocumentedTests.java deleted file mode 100644 index e269f9b9f83..00000000000 --- a/config/src/test/java/org/springframework/security/config/doc/XsdDocumentedTests.java +++ /dev/null @@ -1,320 +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.doc; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import org.springframework.core.io.ClassPathResource; -import org.springframework.security.config.http.SecurityFiltersAssertions; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests to ensure that the xsd is properly documented. - * - * @author Rob Winch - * @author Josh Cummings - */ -public class XsdDocumentedTests { - - // @formatter:off - Collection ignoredIds = Arrays.asList("nsa-any-user-service", - "nsa-any-user-service-parents", - "nsa-authentication", - "nsa-websocket-security", - "nsa-ldap", - "nsa-web", - // deprecated and for removal - "nsa-frame-options-strategy", - "nsa-frame-options-ref", - "nsa-frame-options-value", - "nsa-frame-options-from-parameter"); - // @formatter:on - - String referenceLocation = "../docs/modules/ROOT/pages/servlet/appendix/namespace"; - - String schema31xDocumentLocation = "org/springframework/security/config/spring-security-3.1.xsd"; - - String schemaDocumentLocation = "org/springframework/security/config/spring-security-7.0.xsd"; - - XmlSupport xml = new XmlSupport(); - - @AfterEach - public void close() throws IOException { - this.xml.close(); - } - - @Test - public void parseWhenLatestXsdThenAllNamedSecurityFiltersAreDefinedAndOrderedProperly() throws IOException { - XmlNode root = this.xml.parse(this.schemaDocumentLocation); - // @formatter:off - List nodes = root.child("schema") - .map(XmlNode::children) - .orElse(Stream.empty()) - .filter((node) -> "simpleType".equals(node.simpleName()) - && "named-security-filter".equals(node.attribute("name"))) - .flatMap(XmlNode::children) - .flatMap(XmlNode::children) - .map((node) -> node.attribute("value")) - .filter(StringUtils::hasText) - .collect(Collectors.toList()); - // @formatter:on - SecurityFiltersAssertions.assertEquals(nodes); - } - - @Test - public void parseWhen31XsdThenAllNamedSecurityFiltersAreDefinedAndOrderedProperly() throws IOException { - // @formatter:off - List expected = Arrays.asList("FIRST", - "CHANNEL_FILTER", - "SECURITY_CONTEXT_FILTER", - "CONCURRENT_SESSION_FILTER", - "LOGOUT_FILTER", - "X509_FILTER", - "PRE_AUTH_FILTER", - "CAS_FILTER", - "FORM_LOGIN_FILTER", - "OPENID_FILTER", - "LOGIN_PAGE_FILTER", - "DIGEST_AUTH_FILTER", - "BASIC_AUTH_FILTER", - "REQUEST_CACHE_FILTER", - "SERVLET_API_SUPPORT_FILTER", - "JAAS_API_SUPPORT_FILTER", - "REMEMBER_ME_FILTER", - "ANONYMOUS_FILTER", - "SESSION_MANAGEMENT_FILTER", - "EXCEPTION_TRANSLATION_FILTER", - "FILTER_SECURITY_INTERCEPTOR", - "SWITCH_USER_FILTER", - "LAST"); - // @formatter:on - XmlNode root = this.xml.parse(this.schema31xDocumentLocation); - // @formatter:off - List nodes = root.child("schema") - .map(XmlNode::children) - .orElse(Stream.empty()) - .filter((node) -> "simpleType".equals(node.simpleName()) - && "named-security-filter".equals(node.attribute("name"))) - .flatMap(XmlNode::children) - .flatMap(XmlNode::children) - .map((node) -> node.attribute("value")) - .filter(StringUtils::hasText) - .collect(Collectors.toList()); - // @formatter:on - assertThat(nodes).isEqualTo(expected); - } - - /** - * This will check to ensure that the expected number of xsd documents are found to - * ensure that we are validating against the current xsd document. If this test fails, - * all that is needed is to update the schemaDocument and the expected size for this - * test. - * @return - */ - @Test - public void sizeWhenReadingFilesystemThenIsCorrectNumberOfSchemaFiles() throws IOException { - ClassPathResource resource = new ClassPathResource(this.schemaDocumentLocation); - // @formatter:off - String[] schemas = resource.getFile() - .getParentFile() - .list((dir, name) -> name.endsWith(".xsd")); - // @formatter:on - assertThat(schemas.length) - .withFailMessage("the count is equal to 28, if not then schemaDocument needs updating") - .isEqualTo(28); - } - - /** - * This uses a naming convention for the ids of the appendix to ensure that the entire - * appendix is documented. The naming convention for the ids is documented in - * {@link Element#getIds()}. - * @return - */ - @Test - public void countReferencesWhenReviewingDocumentationThenEntireSchemaIsIncluded() throws IOException { - Map elementsByElementName = this.xml.elementsByElementName(this.schemaDocumentLocation); - // @formatter:off - List documentIds = namespaceLines() - .filter((line) -> line.matches("\\[\\[(nsa-.*)\\]\\]")) - .map((line) -> line.substring(2, line.length() - 2)) - .collect(Collectors.toList()); - Set expectedIds = elementsByElementName.values() - .stream() - .flatMap((element) -> element.getIds().stream()) - .collect(Collectors.toSet()); - // @formatter:on - documentIds.removeAll(this.ignoredIds); - expectedIds.removeAll(this.ignoredIds); - assertThat(documentIds).containsAll(expectedIds); - assertThat(expectedIds).containsAll(documentIds); - } - - /** - * This test ensures that any element that has children or parents contains a section - * that has links pointing to that documentation. - * @return - */ - @Test - public void countLinksWhenReviewingDocumentationThenParentsAndChildrenAreCorrectlyLinked() throws IOException { - Map> docAttrNameToChildren = new TreeMap<>(); - Map> docAttrNameToParents = new TreeMap<>(); - String docAttrName = null; - Map> currentDocAttrNameToElmt = null; - List lines = namespaceLines().collect(Collectors.toList()); - for (String line : lines) { - if (line.matches("^\\[\\[.*\\]\\]$")) { - String id = line.substring(2, line.length() - 2); - if (id.endsWith("-children")) { - docAttrName = id.substring(0, id.length() - 9); - currentDocAttrNameToElmt = docAttrNameToChildren; - } - else if (id.endsWith("-parents")) { - docAttrName = id.substring(0, id.length() - 8); - currentDocAttrNameToElmt = docAttrNameToParents; - } - else if (id.endsWith("-attributes") || docAttrName != null && !id.startsWith(docAttrName)) { - currentDocAttrNameToElmt = null; - docAttrName = null; - } - } - if (docAttrName != null && currentDocAttrNameToElmt != null) { - String expression = ".*<<(nsa-.*),.*>>.*"; - if (line.matches(expression)) { - String elmtId = line.replaceAll(expression, "$1"); - currentDocAttrNameToElmt.computeIfAbsent(docAttrName, (key) -> new ArrayList<>()).add(elmtId); - } - else { - expression = ".*xref:.*#(nsa-.*)\\[.*\\]"; - if (line.matches(expression)) { - String elmtId = line.replaceAll(expression, "$1"); - currentDocAttrNameToElmt.computeIfAbsent(docAttrName, (key) -> new ArrayList<>()).add(elmtId); - } - } - } - } - Map elementNameToElement = this.xml.elementsByElementName(this.schemaDocumentLocation); - Map> schemaAttrNameToChildren = new TreeMap<>(); - Map> schemaAttrNameToParents = new TreeMap<>(); - elementNameToElement.entrySet().stream().forEach((entry) -> { - String key = "nsa-" + entry.getKey(); - if (this.ignoredIds.contains(key)) { - return; - } - // @formatter:off - List parentIds = entry.getValue() - .getAllParentElmts() - .values() - .stream() - .filter((element) -> !this.ignoredIds.contains(element.getId())) - .map((element) -> element.getId()) - .sorted() - .collect(Collectors.toList()); - // @formatter:on - if (!parentIds.isEmpty()) { - schemaAttrNameToParents.put(key, parentIds); - } - // @formatter:off - List childIds = entry.getValue() - .getAllChildElmts() - .values() - .stream() - .filter((element) -> !this.ignoredIds.contains(element.getId())).map((element) -> element.getId()) - .sorted() - .collect(Collectors.toList()); - // @formatter:on - if (!childIds.isEmpty()) { - schemaAttrNameToChildren.put(key, childIds); - } - }); - assertThat(docAttrNameToChildren) - .describedAs(toString(docAttrNameToChildren) + "\n!=\n\n" + toString(schemaAttrNameToChildren)) - .containsExactlyInAnyOrderEntriesOf(schemaAttrNameToChildren); - assertThat(docAttrNameToParents) - .describedAs(toString(docAttrNameToParents) + "\n!=\n\n" + toString(schemaAttrNameToParents)) - .containsExactlyInAnyOrderEntriesOf(schemaAttrNameToParents); - } - - private String toString(Map map) { - StringBuffer buffer = new StringBuffer(); - map.forEach((k, v) -> { - buffer.append(k); - buffer.append("="); - buffer.append(v); - buffer.append("\n"); - }); - return buffer.toString(); - } - - /** - * This test checks each xsd element and ensures there is documentation for it. - * @return - */ - @Test - public void countWhenReviewingDocumentationThenAllElementsDocumented() throws IOException { - Map elementNameToElement = this.xml.elementsByElementName(this.schemaDocumentLocation); - // @formatter:off - String notDocElmtIds = elementNameToElement.values() - .stream() - .filter((element) -> StringUtils.isEmpty(element.getDesc()) - && !this.ignoredIds.contains(element.getId())) - .map((element) -> element.getId()) - .sorted() - .collect(Collectors.joining("\n")); - String notDocAttrIds = elementNameToElement.values() - .stream() - .flatMap((element) -> element.getAttrs().stream()) - .filter((element) -> StringUtils.isEmpty(element.getDesc()) - && !this.ignoredIds.contains(element.getId())) - .map((element) -> element.getId()) - .sorted() - .collect(Collectors.joining("\n")); - // @formatter:on - assertThat(notDocElmtIds).isEmpty(); - assertThat(notDocAttrIds).isEmpty(); - } - - private Stream namespaceLines() { - return Stream.of(new File(this.referenceLocation).listFiles()).map(File::toPath).flatMap(this::fileLines); - } - - private Stream fileLines(Path path) { - try { - return Files.lines(path); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/AccessDeniedConfigTests.java b/config/src/test/java/org/springframework/security/config/http/AccessDeniedConfigTests.java deleted file mode 100644 index 80c92de2259..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/AccessDeniedConfigTests.java +++ /dev/null @@ -1,98 +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.http; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.http.HttpStatus; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Luke Taylor - * @author Josh Cummings - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class AccessDeniedConfigTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/AccessDeniedConfigTests"; - - @Autowired - MockMvc mvc; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - public void configureWhenAccessDeniedHandlerIsMissingLeadingSlashThenException() { - SpringTestContext context = this.spring.configLocations(this.xml("NoLeadingSlash")); - /* - * NOTE: Original error message "errorPage must begin with '/'" no longer shows up - * in stack trace as of Spring Framework 6.x. - * - * See https://github.com/spring-projects/spring-framework/issues/25162. - */ - assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() -> context.autowire()) - .havingRootCause() - .withMessageContaining("Property 'errorPage' threw exception"); - } - - @Test - @WithMockUser - public void configureWhenAccessDeniedHandlerRefThenAutowire() throws Exception { - this.spring.configLocations(this.xml("AccessDeniedHandler")).autowire(); - this.mvc.perform(get("/")).andExpect(status().is(HttpStatus.GONE.value())); - } - - @Test - public void configureWhenAccessDeniedHandlerUsesPathAndRefThenException() { - SpringTestContext context = this.spring.configLocations(this.xml("UsesPathAndRef")); - assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy(() -> context.autowire()) - .withMessageContaining("attribute error-page cannot be used together with the 'ref' attribute"); - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - public static class GoneAccessDeniedHandler implements AccessDeniedHandler { - - @Override - public void handle(HttpServletRequest request, HttpServletResponse response, - AccessDeniedException accessDeniedException) { - response.setStatus(HttpStatus.GONE.value()); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/CsrfBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/CsrfBeanDefinitionParserTests.java deleted file mode 100644 index 0c78edbd686..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/CsrfBeanDefinitionParserTests.java +++ /dev/null @@ -1,43 +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.http; - -import org.junit.jupiter.api.Test; - -import org.springframework.context.support.ClassPathXmlApplicationContext; - -/** - * @author Ankur Pathak - */ -public class CsrfBeanDefinitionParserTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/CsrfBeanDefinitionParserTests"; - - @Test - public void registerDataValueProcessorOnlyIfNotRegistered() { - try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext()) { - context.setAllowBeanDefinitionOverriding(false); - context.setConfigLocation(this.xml("RegisterDataValueProcessorOnyIfNotRegistered")); - context.refresh(); - } - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java b/config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java deleted file mode 100644 index a0d1d871abc..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java +++ /dev/null @@ -1,645 +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.http; - -import java.net.URI; -import java.util.List; - -import jakarta.servlet.Filter; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.security.test.web.servlet.RequestCacheResultMatcher; -import org.springframework.security.test.web.support.WebTestUtils; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.security.web.csrf.CsrfFilter; -import org.springframework.security.web.csrf.CsrfToken; -import org.springframework.security.web.csrf.CsrfTokenRepository; -import org.springframework.security.web.util.matcher.RequestMatcher; -import org.springframework.stereotype.Controller; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.ResultMatcher; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.servlet.support.RequestDataValueProcessor; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.head; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Rob Winch - * @author Josh Cummings - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class CsrfConfigTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/CsrfConfigTests"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void postWhenDefaultConfigurationThenForbiddenSinceCsrfIsEnabled() throws Exception { - this.spring.configLocations(this.xml("AutoConfig")).autowire(); - // @formatter:off - this.mvc.perform(post("/csrf")) - .andExpect(status().isForbidden()) - .andExpect(csrfCreated()); - // @formatter:on - } - - @Test - public void putWhenDefaultConfigurationThenForbiddenSinceCsrfIsEnabled() throws Exception { - this.spring.configLocations(this.xml("AutoConfig")).autowire(); - // @formatter:off - this.mvc.perform(put("/csrf")) - .andExpect(status().isForbidden()) - .andExpect(csrfCreated()); - // @formatter:on - } - - @Test - public void patchWhenDefaultConfigurationThenForbiddenSinceCsrfIsEnabled() throws Exception { - this.spring.configLocations(this.xml("AutoConfig")).autowire(); - // @formatter:off - this.mvc.perform(patch("/csrf")) - .andExpect(status().isForbidden()) - .andExpect(csrfCreated()); - // @formatter:on - } - - @Test - public void deleteWhenDefaultConfigurationThenForbiddenSinceCsrfIsEnabled() throws Exception { - this.spring.configLocations(this.xml("AutoConfig")).autowire(); - // @formatter:off - this.mvc.perform(delete("/csrf")) - .andExpect(status().isForbidden()) - .andExpect(csrfCreated()); - // @formatter:on - } - - @Test - public void invalidWhenDefaultConfigurationThenForbiddenSinceCsrfIsEnabled() throws Exception { - this.spring.configLocations(this.xml("AutoConfig")).autowire(); - // @formatter:off - this.mvc.perform(request("INVALID", new URI("/csrf"))) - .andExpect(status().isForbidden()) - .andExpect(csrfCreated()); - // @formatter:on - } - - @Test - public void getWhenDefaultConfigurationThenCsrfIsEnabled() throws Exception { - this.spring.configLocations(this.xml("shared-controllers"), this.xml("AutoConfig")).autowire(); - // @formatter:off - this.mvc.perform(get("/csrf")) - .andExpect(csrfInBody()); - // @formatter:on - } - - @Test - public void headWhenDefaultConfigurationThenCsrfIsEnabled() throws Exception { - this.spring.configLocations(this.xml("shared-controllers"), this.xml("AutoConfig")).autowire(); - // @formatter:off - this.mvc.perform(head("/csrf-in-header")) - .andExpect(csrfInHeader()); - // @formatter:on - } - - @Test - public void traceWhenDefaultConfigurationThenCsrfIsEnabled() throws Exception { - this.spring.configLocations(this.xml("shared-controllers"), this.xml("AutoConfig")).autowire(); - // @formatter:off - MockMvc traceEnabled = MockMvcBuilders.webAppContextSetup(this.spring.getContext()) - .apply(springSecurity()) - .addDispatcherServletCustomizer((dispatcherServlet) -> dispatcherServlet.setDispatchTraceRequest(true)) - .build(); - traceEnabled.perform(request(HttpMethod.TRACE, "/csrf-in-header")) - .andExpect(csrfInHeader()); - // @formatter:on - } - - @Test - public void optionsWhenDefaultConfigurationThenCsrfIsEnabled() throws Exception { - this.spring.configLocations(this.xml("shared-controllers"), this.xml("AutoConfig")).autowire(); - // @formatter:off - this.mvc.perform(options("/csrf-in-header")) - .andExpect(csrfInHeader()); - // @formatter:on - } - - @Test - public void postWhenCsrfDisabledThenRequestAllowed() throws Exception { - this.spring.configLocations(this.xml("shared-controllers"), this.xml("CsrfDisabled")).autowire(); - // @formatter:off - this.mvc.perform(post("/ok")) - .andExpect(status().isOk()); - // @formatter:on - assertThat(getFilter(this.spring, CsrfFilter.class)).isNull(); - } - - @Test - public void postWhenCsrfElementEnabledThenForbidden() throws Exception { - this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); - // @formatter:off - this.mvc.perform(post("/csrf")) - .andExpect(status().isForbidden()) - .andExpect(csrfCreated()); - // @formatter:on - } - - @Test - public void putWhenCsrfElementEnabledThenForbidden() throws Exception { - this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); - // @formatter:off - this.mvc.perform(put("/csrf")) - .andExpect(status().isForbidden()) - .andExpect(csrfCreated()); - // @formatter:on - } - - @Test - public void patchWhenCsrfElementEnabledThenForbidden() throws Exception { - this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); - // @formatter:off - this.mvc.perform(patch("/csrf")) - .andExpect(status().isForbidden()) - .andExpect(csrfCreated()); - // @formatter:on - } - - @Test - public void deleteWhenCsrfElementEnabledThenForbidden() throws Exception { - this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); - // @formatter:off - this.mvc.perform(delete("/csrf")) - .andExpect(status().isForbidden()) - .andExpect(csrfCreated()); - // @formatter:on - } - - @Test - public void invalidWhenCsrfElementEnabledThenForbidden() throws Exception { - this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); - // @formatter:off - this.mvc.perform(request("INVALID", new URI("/csrf"))) - .andExpect(status().isForbidden()) - .andExpect(csrfCreated()); - // @formatter:on - } - - @Test - public void getWhenCsrfElementEnabledThenOk() throws Exception { - this.spring.configLocations(this.xml("shared-controllers"), this.xml("CsrfEnabled")).autowire(); - // @formatter:off - this.mvc.perform(get("/csrf")) - .andExpect(csrfInBody()); - // @formatter:on - } - - @Test - public void headWhenCsrfElementEnabledThenOk() throws Exception { - this.spring.configLocations(this.xml("shared-controllers"), this.xml("CsrfEnabled")).autowire(); - // @formatter:off - this.mvc.perform(head("/csrf-in-header")) - .andExpect(csrfInHeader()); - // @formatter:on - } - - @Test - public void traceWhenCsrfElementEnabledThenOk() throws Exception { - this.spring.configLocations(this.xml("shared-controllers"), this.xml("CsrfEnabled")).autowire(); - // @formatter:off - MockMvc traceEnabled = MockMvcBuilders.webAppContextSetup(this.spring.getContext()) - .apply(springSecurity()) - .addDispatcherServletCustomizer((dispatcherServlet) -> dispatcherServlet.setDispatchTraceRequest(true)) - .build(); - // @formatter:on - traceEnabled.perform(request(HttpMethod.TRACE, "/csrf-in-header")).andExpect(csrfInHeader()); - } - - @Test - public void optionsWhenCsrfElementEnabledThenOk() throws Exception { - this.spring.configLocations(this.xml("shared-controllers"), this.xml("CsrfEnabled")).autowire(); - // @formatter:off - this.mvc.perform(options("/csrf-in-header")) - .andExpect(csrfInHeader()); - // @formatter:on - } - - @Test - public void autowireWhenCsrfElementEnabledThenCreatesCsrfRequestDataValueProcessor() { - this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); - assertThat(this.spring.getContext().getBean(RequestDataValueProcessor.class)).isNotNull(); - } - - @Test - public void postWhenUsingCsrfAndCustomAccessDeniedHandlerThenTheHandlerIsAppropriatelyEngaged() throws Exception { - this.spring.configLocations(this.xml("WithAccessDeniedHandler"), this.xml("shared-access-denied-handler")) - .autowire(); - // @formatter:off - this.mvc.perform(post("/ok")) - .andExpect(status().isIAmATeapot()); - // @formatter:on - } - - @Test - public void getWhenUsingCsrfAndCustomRequestAttributeThenSetUsingCsrfAttrName() throws Exception { - this.spring.configLocations(this.xml("WithRequestAttrName")).autowire(); - // @formatter:off - MvcResult result = this.mvc.perform(get("/ok")).andReturn(); - assertThat(result.getRequest().getAttribute("csrf-attribute-name")).isInstanceOf(CsrfToken.class); - // @formatter:on - } - - @Test - public void postWhenUsingCsrfAndXorCsrfTokenRequestAttributeHandlerThenOk() throws Exception { - this.spring.configLocations(this.xml("WithXorCsrfTokenRequestAttributeHandler"), this.xml("shared-controllers")) - .autowire(); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/ok")) - .andExpect(status().isOk()) - .andReturn(); - MockHttpSession session = (MockHttpSession) mvcResult.getRequest().getSession(); - MockHttpServletRequestBuilder ok = post("/ok") - .with(csrf()) - .session(session); - this.mvc.perform(ok).andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void postWhenUsingCsrfAndXorCsrfTokenRequestAttributeHandlerWithRawTokenThenForbidden() throws Exception { - this.spring.configLocations(this.xml("WithXorCsrfTokenRequestAttributeHandler"), this.xml("shared-controllers")) - .autowire(); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/csrf")) - .andExpect(status().isOk()) - .andReturn(); - MockHttpServletRequest request = mvcResult.getRequest(); - MockHttpSession session = (MockHttpSession) request.getSession(); - CsrfTokenRepository repository = WebTestUtils.getCsrfTokenRepository(request); - CsrfToken csrfToken = repository.loadToken(request); - MockHttpServletRequestBuilder ok = post("/ok") - .header(csrfToken.getHeaderName(), csrfToken.getToken()) - .session(session); - this.mvc.perform(ok).andExpect(status().isForbidden()); - // @formatter:on - } - - @Test - public void postWhenUsingCsrfAndXorCsrfTokenRequestAttributeHandlerThenCsrfAuthenticationStrategyUses() - throws Exception { - this.spring.configLocations(this.xml("WithXorCsrfTokenRequestAttributeHandler"), this.xml("shared-controllers")) - .autowire(); - // @formatter:off - MvcResult mvcResult1 = this.mvc.perform(get("/csrf")) - .andExpect(status().isOk()) - .andReturn(); - // @formatter:on - MockHttpServletRequest request1 = mvcResult1.getRequest(); - MockHttpSession session = (MockHttpSession) request1.getSession(); - CsrfTokenRepository repository = WebTestUtils.getCsrfTokenRepository(request1); - // @formatter:off - MockHttpServletRequestBuilder login = post("/login") - .param("username", "user") - .param("password", "password") - .session(session) - .with(csrf()); - this.mvc.perform(login) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/")); - // @formatter:on - assertThat(repository.loadToken(request1)).isNull(); - // @formatter:off - MvcResult mvcResult2 = this.mvc.perform(get("/csrf").session(session)) - .andExpect(status().isOk()) - .andReturn(); - // @formatter:on - MockHttpServletRequest request2 = mvcResult2.getRequest(); - CsrfToken csrfToken = repository.loadToken(request2); - CsrfToken csrfTokenAttribute = (CsrfToken) request2.getAttribute(CsrfToken.class.getName()); - assertThat(csrfTokenAttribute).isNotNull(); - assertThat(csrfTokenAttribute.getToken()).isNotBlank(); - assertThat(csrfTokenAttribute.getToken()).isNotEqualTo(csrfToken.getToken()); - } - - @Test - public void postWhenHasCsrfTokenButSessionExpiresThenRequestIsCancelledAfterSuccessfulAuthentication() - throws Exception { - this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); - // simulates a request that has no authentication (e.g. session time-out) - MvcResult result = this.mvc.perform(post("/authenticated").with(csrf())) - .andExpect(redirectedUrl("/login")) - .andReturn(); - MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); - // if the request cache is consulted, then it will redirect back to /some-url, - // which we don't want - // @formatter:off - MockHttpServletRequestBuilder login = post("/login") - .param("username", "user") - .param("password", "password") - .session(session) - .with(csrf()); - this.mvc.perform(login) - .andExpect(redirectedUrl("/")); - // @formatter:on - } - - @Test - public void getWhenHasCsrfTokenButSessionExpiresThenRequestIsRememeberedAfterSuccessfulAuthentication() - throws Exception { - this.spring.configLocations(this.xml("CsrfEnabled")).autowire(); - // simulates a request that has no authentication (e.g. session time-out) - MvcResult result = this.mvc.perform(get("/authenticated")).andExpect(redirectedUrl("/login")).andReturn(); - MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); - // if the request cache is consulted, then it will redirect back to /some-url, - // which we do want - // @formatter:off - MockHttpServletRequestBuilder login = post("/login") - .param("username", "user") - .param("password", "password") - .session(session) - .with(csrf()); - this.mvc.perform(login) - .andExpect(RequestCacheResultMatcher.redirectToCachedRequest()); - // @formatter:on - } - - /** - * SEC-2422: csrf expire CSRF token and session-management invalid-session-url - */ - @Test - public void postWhenUsingCsrfAndCustomSessionManagementAndNoSessionThenStillRedirectsToInvalidSessionUrl() - throws Exception { - this.spring.configLocations(this.xml("WithSessionManagement")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder postToOk = post("/ok") - .param("_csrf", "abc"); - MvcResult result = this.mvc.perform(postToOk) - .andExpect(redirectedUrl("/error/sessionError")) - .andReturn(); - MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); - this.mvc.perform(post("/csrf").session(session)) - .andExpect(status().isForbidden()); - // @formatter:on - } - - @Test - public void requestWhenUsingCustomRequestMatcherConfiguredThenAppliesAccordingly() throws Exception { - SpringTestContext context = this.spring.configLocations(this.xml("shared-controllers"), - this.xml("WithRequestMatcher"), this.xml("mock-request-matcher")); - context.autowire(); - RequestMatcher matcher = context.getContext().getBean(RequestMatcher.class); - given(matcher.matches(any(HttpServletRequest.class))).willReturn(false); - // @formatter:off - this.mvc.perform(post("/ok")) - .andExpect(status().isOk()); - // @formatter:on - given(matcher.matches(any(HttpServletRequest.class))).willReturn(true); - // @formatter:off - this.mvc.perform(get("/ok")) - .andExpect(status().isForbidden()); - // @formatter:on - } - - @Test - public void getWhenDefaultConfigurationThenSessionNotImmediatelyCreated() throws Exception { - this.spring.configLocations(this.xml("shared-controllers"), this.xml("AutoConfig")).autowire(); - // @formatter:off - MvcResult result = this.mvc.perform(get("/ok")) - .andExpect(status().isOk()).andReturn(); - // @formatter:on - assertThat(result.getRequest().getSession(false)).isNull(); - } - - @Test - @WithMockUser - public void postWhenCsrfMismatchesThenForbidden() throws Exception { - this.spring.configLocations(this.xml("shared-controllers"), this.xml("AutoConfig")).autowire(); - MvcResult result = this.mvc.perform(get("/ok")).andReturn(); - MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); - // @formatter:off - MockHttpServletRequestBuilder postOk = post("/ok") - .session(session) - .with(csrf().useInvalidToken()); - this.mvc.perform(postOk) - .andExpect(status().isForbidden()); - // @formatter:on - } - - @Test - public void loginWhenDefaultConfigurationThenCsrfCleared() throws Exception { - this.spring.configLocations(this.xml("shared-controllers"), this.xml("AutoConfig")).autowire(); - MvcResult result = this.mvc.perform(get("/csrf")).andReturn(); - MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .param("username", "user") - .param("password", "password") - .session(session) - .with(csrf()); - this.mvc.perform(loginRequest) - .andExpect(status().isFound()); - this.mvc.perform(get("/csrf").session(session)) - .andExpect(csrfChanged(result)); - // @formatter:on - } - - @Test - public void logoutWhenDefaultConfigurationThenCsrfCleared() throws Exception { - this.spring.configLocations(this.xml("shared-controllers"), this.xml("AutoConfig")).autowire(); - MvcResult result = this.mvc.perform(get("/csrf")).andReturn(); - MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); - // @formatter:off - this.mvc.perform(post("/logout").session(session).with(csrf())) - .andExpect(status().isFound()); - this.mvc.perform(get("/csrf").session(session)) - .andExpect(csrfChanged(result)); - // @formatter:on - } - - /** - * SEC-2495: csrf disables logout on GET - */ - @Test - @WithMockUser - public void logoutWhenDefaultConfigurationThenDisabled() throws Exception { - this.spring.configLocations(this.xml("shared-controllers"), this.xml("CsrfEnabled")).autowire(); - // renders form to log out but does not perform a redirect - // @formatter:off - this.mvc.perform(get("/logout")) - .andExpect(status().isOk()); - // @formatter:on - // still logged in - // @formatter:off - this.mvc.perform(get("/authenticated")) - .andExpect(status().isOk()); - // @formatter:on - } - - private T getFilter(SpringTestContext context, Class type) { - FilterChainProxy chain = context.getContext().getBean(FilterChainProxy.class); - List filters = chain.getFilters("/any"); - for (Filter filter : filters) { - if (type.isAssignableFrom(filter.getClass())) { - return (T) filter; - } - } - return null; - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - ResultMatcher csrfChanged(MvcResult first) { - return (second) -> { - assertThat(first).isNotNull(); - assertThat(second).isNotNull(); - assertThat(first.getResponse().getContentAsString()) - .isNotEqualTo(second.getResponse().getContentAsString()); - }; - } - - ResultMatcher csrfCreated() { - return new CsrfCreatedResultMatcher(); - } - - ResultMatcher csrfInHeader() { - return new CsrfReturnedResultMatcher((result) -> result.getResponse().getHeader("X-CSRF-TOKEN")); - } - - ResultMatcher csrfInBody() { - return new CsrfReturnedResultMatcher((result) -> result.getResponse().getContentAsString()); - } - - @Controller - public static class RootController { - - @RequestMapping(value = "/csrf-in-header", - method = { RequestMethod.HEAD, RequestMethod.TRACE, RequestMethod.OPTIONS }) - @ResponseBody - String csrfInHeaderAndBody(CsrfToken token, HttpServletResponse response) { - response.setHeader(token.getHeaderName(), token.getToken()); - return csrfInBody(token); - } - - @RequestMapping(value = "/csrf", - method = { RequestMethod.POST, RequestMethod.PUT, RequestMethod.PATCH, RequestMethod.DELETE, - RequestMethod.GET }) - @ResponseBody - String csrfInBody(CsrfToken token) { - return token.getToken(); - } - - @RequestMapping(value = "/ok", method = { RequestMethod.POST, RequestMethod.GET }) - @ResponseBody - String ok() { - return "ok"; - } - - @GetMapping("/authenticated") - @ResponseBody - String authenticated() { - return "authenticated"; - } - - } - - private static class TeapotAccessDeniedHandler implements AccessDeniedHandler { - - @Override - public void handle(HttpServletRequest request, HttpServletResponse response, - AccessDeniedException accessDeniedException) { - response.setStatus(HttpStatus.I_AM_A_TEAPOT.value()); - } - - } - - @FunctionalInterface - interface ExceptionalFunction { - - OUT apply(IN in) throws Exception; - - } - - static class CsrfCreatedResultMatcher implements ResultMatcher { - - @Override - public void match(MvcResult result) { - MockHttpServletRequest request = result.getRequest(); - CsrfToken token = WebTestUtils.getCsrfTokenRepository(request).loadToken(request); - assertThat(token).isNotNull(); - } - - } - - static class CsrfReturnedResultMatcher implements ResultMatcher { - - ExceptionalFunction token; - - CsrfReturnedResultMatcher(ExceptionalFunction token) { - this.token = token; - } - - @Override - public void match(MvcResult result) throws Exception { - MockHttpServletRequest request = result.getRequest(); - CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); - assertThat(token).isNotNull(); - assertThat(token.getToken()).isEqualTo(this.token.apply(result)); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/DefaultFilterChainValidatorTests.java b/config/src/test/java/org/springframework/security/config/http/DefaultFilterChainValidatorTests.java deleted file mode 100644 index 4bed10dfb82..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/DefaultFilterChainValidatorTests.java +++ /dev/null @@ -1,162 +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.http; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import jakarta.servlet.http.HttpServletRequest; -import org.apache.commons.logging.Log; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.security.access.AccessDecisionManager; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.DefaultSecurityFilterChain; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.UnreachableFilterChainException; -import org.springframework.security.web.access.ExceptionTranslationFilter; -import org.springframework.security.web.access.intercept.AuthorizationFilter; -import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource; -import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource; -import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; -import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; -import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.security.web.util.matcher.AnyRequestMatcher; -import org.springframework.test.util.ReflectionTestUtils; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willThrow; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * @author Rob Winch - */ -@ExtendWith(MockitoExtension.class) -public class DefaultFilterChainValidatorTests { - - private DefaultFilterChainValidator validator; - - private FilterChainProxy chain; - - private FilterChainProxy chainAuthorizationFilter; - - @Mock - private Log logger; - - @Mock - private DefaultFilterInvocationSecurityMetadataSource metadataSource; - - @Mock - private AccessDecisionManager accessDecisionManager; - - private FilterSecurityInterceptor authorizationInterceptor; - - @Mock - private AuthorizationManager authorizationManager; - - private AuthorizationFilter authorizationFilter; - - @BeforeEach - public void setUp() { - AnonymousAuthenticationFilter aaf = new AnonymousAuthenticationFilter("anonymous"); - this.authorizationInterceptor = new FilterSecurityInterceptor(); - this.authorizationInterceptor.setAccessDecisionManager(this.accessDecisionManager); - this.authorizationInterceptor.setSecurityMetadataSource(this.metadataSource); - this.authorizationFilter = new AuthorizationFilter(this.authorizationManager); - AuthenticationEntryPoint authenticationEntryPoint = new LoginUrlAuthenticationEntryPoint("/login"); - ExceptionTranslationFilter etf = new ExceptionTranslationFilter(authenticationEntryPoint); - DefaultSecurityFilterChain securityChain = new DefaultSecurityFilterChain(AnyRequestMatcher.INSTANCE, aaf, etf, - this.authorizationInterceptor); - this.chain = new FilterChainProxy(securityChain); - DefaultSecurityFilterChain securityChainAuthorizationFilter = new DefaultSecurityFilterChain( - AnyRequestMatcher.INSTANCE, aaf, etf, this.authorizationFilter); - this.chainAuthorizationFilter = new FilterChainProxy(securityChainAuthorizationFilter); - this.validator = new DefaultFilterChainValidator(); - ReflectionTestUtils.setField(this.validator, "logger", this.logger); - } - - @Test - void validateWhenFilterSecurityInterceptorConfiguredThenValidates() { - assertThatNoException().isThrownBy(() -> this.validator.validate(this.chain)); - } - - // SEC-1878 - @SuppressWarnings("unchecked") - @Test - public void validateCheckLoginPageIsntProtectedThrowsIllegalArgumentException() { - IllegalArgumentException toBeThrown = new IllegalArgumentException("failed to eval expression"); - willThrow(toBeThrown).given(this.accessDecisionManager) - .decide(any(Authentication.class), any(), any(Collection.class)); - this.validator.validate(this.chain); - verify(this.logger).info( - "Unable to check access to the login page to determine if anonymous access is allowed. This might be an error, but can happen under normal circumstances.", - toBeThrown); - } - - @Test - public void validateCheckLoginPageAllowsAnonymous() { - given(this.authorizationManager.authorize(any(), any())).willReturn(new AuthorizationDecision(false)); - this.validator.validate(this.chainAuthorizationFilter); - verify(this.logger).warn("Anonymous access to the login page doesn't appear to be enabled. " - + "This is almost certainly an error. Please check your configuration allows unauthenticated " - + "access to the configured login page. (Simulated access was rejected)"); - } - - // SEC-1957 - @Test - public void validateCustomMetadataSource() { - FilterInvocationSecurityMetadataSource customMetaDataSource = mock( - FilterInvocationSecurityMetadataSource.class); - this.authorizationInterceptor.setSecurityMetadataSource(customMetaDataSource); - this.validator.validate(this.chain); - verify(customMetaDataSource, atLeastOnce()).getAttributes(any()); - } - - @Test - void validateWhenSameRequestMatchersArePresentThenUnreachableFilterChainException() { - PathPatternRequestMatcher.Builder builder = PathPatternRequestMatcher.withDefaults(); - AnonymousAuthenticationFilter authenticationFilter = mock(AnonymousAuthenticationFilter.class); - ExceptionTranslationFilter exceptionTranslationFilter = mock(ExceptionTranslationFilter.class); - SecurityFilterChain chain1 = new DefaultSecurityFilterChain(builder.matcher("/api"), authenticationFilter, - exceptionTranslationFilter, this.authorizationInterceptor); - SecurityFilterChain chain2 = new DefaultSecurityFilterChain(builder.matcher("/api"), authenticationFilter, - exceptionTranslationFilter, this.authorizationInterceptor); - List chains = new ArrayList<>(); - chains.add(chain2); - chains.add(chain1); - FilterChainProxy proxy = new FilterChainProxy(chains); - - assertThatExceptionOfType(UnreachableFilterChainException.class) - .isThrownBy(() -> this.validator.validate(proxy)); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/DeferHttpSessionXmlConfigTests.java b/config/src/test/java/org/springframework/security/config/http/DeferHttpSessionXmlConfigTests.java deleted file mode 100644 index d8b7a4f2c5b..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/DeferHttpSessionXmlConfigTests.java +++ /dev/null @@ -1,82 +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.http; - -import jakarta.servlet.FilterChain; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.web.FilterChainProxy; - -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -/** - * @author Rob Winch - */ -@ExtendWith(SpringTestContextExtension.class) -public class DeferHttpSessionXmlConfigTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/DeferHttpSessionTests"; - - @Autowired - FilterChainProxy springSecurityFilterChain; - - @Autowired - private Service service; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - public void explicitDeferHttpSession() throws Exception { - this.spring.configLocations(xml("Explicit")).autowire(); - - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); - MockHttpServletRequest mockRequest = spy(request); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain chain = (httpRequest, httpResponse) -> httpResponse.getWriter().write(this.service.getMessage()); - - this.springSecurityFilterChain.doFilter(mockRequest, response, chain); - - verify(mockRequest, never()).isRequestedSessionIdValid(); - verify(mockRequest, never()).changeSessionId(); - verify(mockRequest, never()).getSession(anyBoolean()); - verify(mockRequest, never()).getSession(); - } - - private static String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - public static class Service { - - @PreAuthorize("permitAll") - public String getMessage() { - return "message"; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/FilterSecurityMetadataSourceBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/FilterSecurityMetadataSourceBeanDefinitionParserTests.java deleted file mode 100644 index fda4379439c..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/FilterSecurityMetadataSourceBeanDefinitionParserTests.java +++ /dev/null @@ -1,136 +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.http; - -import java.util.Collection; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.context.support.AbstractXmlApplicationContext; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.config.BeanIds; -import org.springframework.security.config.ConfigTestUtils; -import org.springframework.security.config.util.InMemoryXmlApplicationContext; -import org.springframework.security.web.FilterInvocation; -import org.springframework.security.web.access.expression.ExpressionBasedFilterInvocationSecurityMetadataSource; -import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests for {@link FilterInvocationSecurityMetadataSourceParser}. - * - * @author Luke Taylor - */ -public class FilterSecurityMetadataSourceBeanDefinitionParserTests { - - private AbstractXmlApplicationContext appContext; - - @AfterEach - public void closeAppContext() { - if (this.appContext != null) { - this.appContext.close(); - this.appContext = null; - } - } - - private void setContext(String context) { - this.appContext = new InMemoryXmlApplicationContext(context); - } - - @Test - public void parsingMinimalConfigurationIsSuccessful() { - // @formatter:off - setContext("" - + " " - + ""); - // @formatter:on - DefaultFilterInvocationSecurityMetadataSource fids = (DefaultFilterInvocationSecurityMetadataSource) this.appContext - .getBean("fids"); - Collection cad = fids.getAttributes(createFilterInvocation("/anything", "GET")); - assertThat(cad).contains(new SecurityConfig("ROLE_A")); - } - - @Test - public void expressionsAreSupported() { - // @formatter:off - setContext("" - + " " - + ""); - // @formatter:on - ExpressionBasedFilterInvocationSecurityMetadataSource fids = (ExpressionBasedFilterInvocationSecurityMetadataSource) this.appContext - .getBean("fids"); - ConfigAttribute[] cad = fids.getAttributes(createFilterInvocation("/anything", "GET")) - .toArray(new ConfigAttribute[0]); - assertThat(cad).hasSize(1); - assertThat(cad[0].toString()).isEqualTo("hasRole('ROLE_A')"); - } - - // SEC-1201 - @Test - public void interceptUrlsSupportPropertyPlaceholders() { - System.setProperty("secure.url", "/secure"); - System.setProperty("secure.role", "ROLE_A"); - setContext("" - + "" - + " " - + ""); - DefaultFilterInvocationSecurityMetadataSource fids = (DefaultFilterInvocationSecurityMetadataSource) this.appContext - .getBean("fids"); - Collection cad = fids.getAttributes(createFilterInvocation("/secure", "GET")); - assertThat(cad).containsExactly(new SecurityConfig("ROLE_A")); - } - - @Test - public void parsingWithinFilterSecurityInterceptorIsSuccessful() { - // @formatter:off - setContext("" - + "" - + " " - + " " - + " " - + " " - + " " - + " " - + " " - + " " - + "" - + ConfigTestUtils.AUTH_PROVIDER_XML); - // @formatter:on - } - - @Test - public void parsingInterceptUrlServletPathFails() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> setContext("" - + " " - + "")); - } - - private FilterInvocation createFilterInvocation(String path, String method) { - MockHttpServletRequest request = new MockHttpServletRequest(method, path); - return new FilterInvocation(request, new MockHttpServletResponse(), new MockFilterChain()); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/FormLoginBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/FormLoginBeanDefinitionParserTests.java deleted file mode 100644 index b814a14d9b5..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/FormLoginBeanDefinitionParserTests.java +++ /dev/null @@ -1,172 +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.http; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.web.WebAttributes; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; - -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.not; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Luke Taylor - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class FormLoginBeanDefinitionParserTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/FormLoginBeanDefinitionParserTests"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void getLoginWhenAutoConfigThenShowsDefaultLoginPage() throws Exception { - this.spring.configLocations(this.xml("Simple")).autowire(); - String expectedContent = """ - - - - - - - - Please sign in - - - -
- - - - -
- - """; - this.mvc.perform(get("/login")).andExpect(content().string(expectedContent)); - } - - @Test - public void getLogoutWhenAutoConfigThenShowsDefaultLogoutPage() throws Exception { - this.spring.configLocations(this.xml("AutoConfig")).autowire(); - this.mvc.perform(get("/logout")).andExpect(content().string(containsString("action=\"/logout\""))); - } - - @Test - public void getLoginWhenConfiguredWithCustomAttributesThenLoginPageReflects() throws Exception { - this.spring.configLocations(this.xml("WithCustomAttributes")).autowire(); - - String expectedContent = """ - - - - - - - - Please sign in - - - -
- - - - -
- - """; - this.mvc.perform(get("/login")).andExpect(content().string(expectedContent)); - this.mvc.perform(get("/logout")).andExpect(status().is3xxRedirection()); - } - - @Test - public void failedLoginWhenConfiguredWithCustomAuthenticationFailureThenForwardsAccordingly() throws Exception { - this.spring.configLocations(this.xml("WithAuthenticationFailureForwardUrl")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .param("username", "bob") - .param("password", "invalidpassword"); - this.mvc.perform(loginRequest) - .andExpect(status().isOk()) - .andExpect(forwardedUrl("/failure_forward_url")) - .andExpect(request().attribute(WebAttributes.AUTHENTICATION_EXCEPTION, not(nullValue()))); - // @formatter:on - } - - @Test - public void successfulLoginWhenConfiguredWithCustomAuthenticationSuccessThenForwardsAccordingly() throws Exception { - this.spring.configLocations(this.xml("WithAuthenticationSuccessForwardUrl")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .param("username", "user") - .param("password", "password"); - this.mvc.perform(loginRequest) - .andExpect(status().isOk()) - .andExpect(forwardedUrl("/success_forward_url")); - // @formatter:on - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java b/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java deleted file mode 100644 index 48a63615aa7..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java +++ /dev/null @@ -1,255 +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.http; - -import java.util.List; - -import jakarta.servlet.Filter; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.http.HttpStatus; -import org.springframework.security.config.BeanIds; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.verify; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Luke Taylor - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class FormLoginConfigTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/FormLoginConfigTests"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void getProtectedPageWhenFormLoginConfiguredThenRedirectsToDefaultLoginPage() throws Exception { - this.spring.configLocations(this.xml("WithRequestMatcher")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(redirectedUrl("/login")); - // @formatter:on - } - - @Test - public void authenticateWhenDefaultTargetUrlConfiguredThenRedirectsAccordingly() throws Exception { - this.spring.configLocations(this.xml("WithDefaultTargetUrl")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .param("username", "user") - .param("password", "password") - .with(csrf()); - this.mvc.perform(loginRequest) - .andExpect(redirectedUrl("/default")); - // @formatter:on - } - - @Test - public void authenticateWhenConfiguredWithSpelThenRedirectsAccordingly() throws Exception { - this.spring.configLocations(this.xml("UsingSpel")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .param("username", "user") - .param("password", "password") - .with(csrf()); - this.mvc.perform(loginRequest) - .andExpect(redirectedUrl(WebConfigUtilsTests.URL + "/default")); - MockHttpServletRequestBuilder invalidPassword = post("/login") - .param("username", "user") - .param("password", "wrong") - .with(csrf()); - this.mvc.perform(invalidPassword) - .andExpect(redirectedUrl(WebConfigUtilsTests.URL + "/failure")); - this.mvc.perform(get("/")) - .andExpect(redirectedUrl(WebConfigUtilsTests.URL + "/login")); - // @formatter:on - } - - @Test - public void autowireWhenLoginPageIsMisconfiguredThenDetects() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("NoLeadingSlashLoginPage")).autowire()); - } - - @Test - public void autowireWhenDefaultTargetUrlIsMisconfiguredThenDetects() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("NoLeadingSlashDefaultTargetUrl")).autowire()); - } - - @Test - public void authenticateWhenCustomHandlerBeansConfiguredThenInvokesAccordingly() throws Exception { - this.spring.configLocations(this.xml("WithSuccessAndFailureHandlers")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .param("username", "user") - .param("password", "password") - .with(csrf()); - this.mvc.perform(loginRequest) - .andExpect(status().isIAmATeapot()); - MockHttpServletRequestBuilder invalidPassword = post("/login") - .param("username", "user") - .param("password", "wrong") - .with(csrf()); - this.mvc.perform(invalidPassword) - .andExpect(status().isIAmATeapot()); - // @formatter:on - } - - @Test - public void authenticateWhenCustomUsernameAndPasswordParametersThenSucceeds() throws Exception { - this.spring.configLocations(this.xml("WithUsernameAndPasswordParameters")).autowire(); - this.mvc.perform(post("/login").param("xname", "user").param("xpass", "password").with(csrf())) - .andExpect(redirectedUrl("/")); - } - - @Test - public void authenticateWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { - this.spring.configLocations(this.xml("WithCustomSecurityContextHolderStrategy")).autowire(); - SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); - this.mvc.perform(post("/login").with(csrf())).andExpect(redirectedUrl("/login?error")); - verify(strategy, atLeastOnce()).getContext(); - } - - /** - * SEC-2919 - DefaultLoginGeneratingFilter incorrectly used if login-url="/login" - */ - @Test - public void autowireWhenCustomLoginPageIsSlashLoginThenNoDefaultLoginPageGeneratingFilterIsWired() - throws Exception { - this.spring.configLocations(this.xml("ForSec2919")).autowire(); - this.mvc.perform(get("/login")).andExpect(content().string("teapot")); - assertThat(getFilter(this.spring.getContext(), DefaultLoginPageGeneratingFilter.class)).isNull(); - } - - @Test - public void authenticateWhenCsrfIsEnabledThenRequiresToken() throws Exception { - this.spring.configLocations(this.xml("WithCsrfEnabled")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .param("username", "user") - .param("password", "password"); - this.mvc.perform(loginRequest) - .andExpect(status().isForbidden()); - // @formatter:on - } - - @Test - public void authenticateWhenCsrfIsDisabledThenDoesNotRequireToken() throws Exception { - this.spring.configLocations(this.xml("WithCsrfDisabled")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .param("username", "user") - .param("password", "password"); - this.mvc.perform(loginRequest) - .andExpect(status().isFound()); - // @formatter:on - } - - /** - * SEC-3147: authentication-failure-url should be contained "error" parameter if - * login-page="/login" - */ - @Test - public void authenticateWhenLoginPageIsSlashLoginAndAuthenticationFailsThenRedirectContainsErrorParameter() - throws Exception { - this.spring.configLocations(this.xml("ForSec3147")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .param("username", "user") - .param("password", "wrong") - .with(csrf()); - this.mvc.perform(loginRequest) - .andExpect(redirectedUrl("/login?error")); - // @formatter:on - } - - private Filter getFilter(ApplicationContext context, Class filterClass) { - FilterChainProxy filterChain = context.getBean(BeanIds.FILTER_CHAIN_PROXY, FilterChainProxy.class); - List filters = filterChain.getFilters("/any"); - for (Filter filter : filters) { - if (filter.getClass() == filterClass) { - return filter; - } - } - return null; - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - @RestController - public static class LoginController { - - @GetMapping("/login") - public String ok() { - return "teapot"; - } - - } - - public static class TeapotAuthenticationHandler - implements AuthenticationSuccessHandler, AuthenticationFailureHandler { - - @Override - public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, - AuthenticationException exception) { - response.setStatus(HttpStatus.I_AM_A_TEAPOT.value()); - } - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) { - response.setStatus(HttpStatus.I_AM_A_TEAPOT.value()); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java b/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java deleted file mode 100644 index b15b1dc7056..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/HttpConfigTests.java +++ /dev/null @@ -1,177 +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.http; - -import java.util.Iterator; - -import io.micrometer.observation.Observation; -import io.micrometer.observation.ObservationHandler; -import io.micrometer.observation.ObservationRegistry; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpServletResponseWrapper; -import org.apache.http.HttpStatus; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.test.web.servlet.MockMvc; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Rob Winch - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class HttpConfigTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/HttpConfigTests"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void getWhenUsingMinimalConfigurationThenRedirectsToLogin() throws Exception { - this.spring.configLocations(this.xml("Minimal")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); - // @formatter:on - } - - @Test - public void getWhenUsingMinimalAuthorizationManagerThenRedirectsToLogin() throws Exception { - this.spring.configLocations(this.xml("MinimalAuthorizationManager")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); - // @formatter:on - } - - @Test - public void getWhenUsingAuthorizationManagerThenRedirectsToLogin() throws Exception { - this.spring.configLocations(this.xml("AuthorizationManager")).autowire(); - AuthorizationManager authorizationManager = this.spring.getContext() - .getBean(AuthorizationManager.class); - given(authorizationManager.authorize(any(), any())).willReturn(new AuthorizationDecision(false)); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); - // @formatter:on - verify(authorizationManager).authorize(any(), any()); - } - - @Test - public void getWhenUsingMinimalConfigurationThenPreventsSessionAsUrlParameter() throws Exception { - this.spring.configLocations(this.xml("Minimal")).autowire(); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChainProxy proxy = this.spring.getContext().getBean(FilterChainProxy.class); - proxy.doFilter(request, new EncodeUrlDenyingHttpServletResponseWrapper(response), (req, resp) -> { - }); - assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); - assertThat(response.getRedirectedUrl()).isEqualTo("/login"); - } - - @Test - public void getWhenUsingObservationRegistryThenObservesRequest() throws Exception { - this.spring.configLocations(this.xml("WithObservationRegistry")).autowire(); - // @formatter:off - this.mvc.perform(get("/").with(httpBasic("user", "password"))) - .andExpect(status().isNotFound()); - // @formatter:on - ObservationHandler handler = this.spring.getContext().getBean(ObservationHandler.class); - ArgumentCaptor captor = ArgumentCaptor.forClass(Observation.Context.class); - verify(handler, times(5)).onStart(captor.capture()); - Iterator contexts = captor.getAllValues().iterator(); - assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain before"); - assertThat(contexts.next().getName()).isEqualTo("spring.security.authentications"); - assertThat(contexts.next().getName()).isEqualTo("spring.security.authorizations"); - assertThat(contexts.next().getName()).isEqualTo("spring.security.http.secured.requests"); - assertThat(contexts.next().getContextualName()).isEqualTo("security filterchain after"); - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - private static class EncodeUrlDenyingHttpServletResponseWrapper extends HttpServletResponseWrapper { - - EncodeUrlDenyingHttpServletResponseWrapper(HttpServletResponse response) { - super(response); - } - - @Override - public String encodeURL(String url) { - throw new RuntimeException("Unexpected invocation of encodeURL"); - } - - @Override - public String encodeRedirectURL(String url) { - throw new RuntimeException("Unexpected invocation of encodeURL"); - } - - } - - public static final class MockObservationRegistry implements FactoryBean { - - private ObservationHandler handler = mock(ObservationHandler.class); - - @Override - public ObservationRegistry getObject() { - ObservationRegistry registry = ObservationRegistry.create(); - registry.observationConfig().observationHandler(this.handler); - given(this.handler.supportsContext(any())).willReturn(true); - return registry; - } - - @Override - public Class getObjectType() { - return ObservationRegistry.class; - } - - public void setHandler(ObservationHandler handler) { - this.handler = handler; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/HttpCorsConfigTests.java b/config/src/test/java/org/springframework/security/config/http/HttpCorsConfigTests.java deleted file mode 100644 index 1e37de7a750..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/HttpCorsConfigTests.java +++ /dev/null @@ -1,161 +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.http; - -import java.util.Arrays; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.ResultMatcher; -import org.springframework.test.web.servlet.request.RequestPostProcessor; -import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Rob Winch - * @author Tim Ysewyn - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class HttpCorsConfigTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/HttpCorsConfigTests"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void autowireWhenMissingMvcThenGivesInformativeError() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("RequiresMvc")).autowire()) - .havingRootCause() - .withMessageContaining( - "Please ensure Spring Security & Spring MVC are configured in a shared ApplicationContext"); - } - - @Test - public void getWhenUsingCorsThenDoesSpringSecurityCorsHandshake() throws Exception { - this.spring.configLocations(this.xml("WithCors")).autowire(); - // @formatter:off - this.mvc.perform(get("/").with(this.approved())) - .andExpect(corsResponseHeaders()) - .andExpect((status().isIAmATeapot())); - this.mvc.perform(options("/").with(this.preflight())) - .andExpect(corsResponseHeaders()) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void getWhenUsingCustomCorsConfigurationSourceThenDoesSpringSecurityCorsHandshake() throws Exception { - this.spring.configLocations(this.xml("WithCorsConfigurationSource")).autowire(); - // @formatter:off - this.mvc.perform(get("/").with(this.approved())) - .andExpect(corsResponseHeaders()) - .andExpect((status().isIAmATeapot())); - this.mvc.perform(options("/").with(this.preflight())) - .andExpect(corsResponseHeaders()) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void getWhenUsingCustomCorsFilterThenDoesSPringSecurityCorsHandshake() throws Exception { - this.spring.configLocations(this.xml("WithCorsFilter")).autowire(); - // @formatter:off - this.mvc.perform(get("/").with(this.approved())) - .andExpect(corsResponseHeaders()) - .andExpect((status().isIAmATeapot())); - this.mvc.perform(options("/").with(this.preflight())) - .andExpect(corsResponseHeaders()) - .andExpect(status().isOk()); - // @formatter:on - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - private RequestPostProcessor preflight() { - return cors(true); - } - - private RequestPostProcessor approved() { - return cors(false); - } - - private RequestPostProcessor cors(boolean preflight) { - return (request) -> { - request.addHeader(HttpHeaders.ORIGIN, "https://example.com"); - if (preflight) { - request.setMethod(HttpMethod.OPTIONS.name()); - request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, HttpMethod.POST.name()); - } - return request; - }; - } - - private ResultMatcher corsResponseHeaders() { - return (result) -> { - header().exists("Access-Control-Allow-Origin").match(result); - header().exists("X-Content-Type-Options").match(result); - }; - } - - @RestController - @CrossOrigin(methods = { RequestMethod.GET, RequestMethod.POST }) - static class CorsController { - - @RequestMapping("/") - String hello() { - return "Hello"; - } - - } - - static class MyCorsConfigurationSource extends UrlBasedCorsConfigurationSource { - - MyCorsConfigurationSource() { - CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList("*")); - configuration.setAllowedMethods(Arrays.asList(RequestMethod.GET.name(), RequestMethod.POST.name())); - super.registerCorsConfiguration("/**", configuration); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java b/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java deleted file mode 100644 index b4c0467c467..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/HttpHeadersConfigTests.java +++ /dev/null @@ -1,970 +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.http; - -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import com.google.common.collect.ImmutableMap; -import jakarta.servlet.http.HttpSession; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.session.SessionLimit; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.ResultMatcher; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Rob Winch - * @author Tim Ysewyn - * @author Josh Cummings - * @author Rafiullah Hamedy - * @author Marcus Da Coregio - * @author Claudenir Freitas - */ -@ExtendWith(SpringTestContextExtension.class) -public class HttpHeadersConfigTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/HttpHeadersConfigTests"; - - // @formatter:off - static final Map defaultHeaders = ImmutableMap.builder() - .put("X-Content-Type-Options", "nosniff").put("X-Frame-Options", "DENY") - .put("Strict-Transport-Security", "max-age=31536000 ; includeSubDomains") - .put("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate") - .put("Expires", "0") - .put("Pragma", "no-cache") - .put("X-XSS-Protection", "0") - .build(); - // @formatter:on - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void requestWhenHeadersDisabledThenResponseExcludesAllSecureHeaders() throws Exception { - this.spring.configLocations(this.xml("HeadersDisabled")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(excludesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenHeadersDisabledViaPlaceholderThenResponseExcludesAllSecureHeaders() throws Exception { - System.setProperty("security.headers.disabled", "true"); - this.spring.configLocations(this.xml("DisabledWithPlaceholder")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(excludesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenHeadersEnabledViaPlaceholderThenResponseIncludesAllSecureHeaders() throws Exception { - System.setProperty("security.headers.disabled", "false"); - this.spring.configLocations(this.xml("DisabledWithPlaceholder")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(includesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenHeadersDisabledRefMissingPlaceholderThenResponseIncludesAllSecureHeaders() throws Exception { - System.clearProperty("security.headers.disabled"); - this.spring.configLocations(this.xml("DisabledWithPlaceholder")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(includesDefaults()); - // @formatter:on - } - - @Test - public void configureWhenHeadersDisabledHavingChildElementThenAutowireFails() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("HeadersDisabledHavingChildElement")).autowire()) - .withMessageContaining("Cannot specify with child elements"); - } - - @Test - public void requestWhenHeadersEnabledThenResponseContainsAllSecureHeaders() throws Exception { - this.spring.configLocations(this.xml("DefaultConfig")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(includesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenHeadersElementUsedThenResponseContainsAllSecureHeaders() throws Exception { - this.spring.configLocations(this.xml("HeadersEnabled")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(includesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenFrameOptionsConfiguredThenIncludesHeader() throws Exception { - Map headers = new HashMap<>(defaultHeaders); - headers.put("X-Frame-Options", "SAMEORIGIN"); - this.spring.configLocations(this.xml("WithFrameOptions")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(includes(headers)); - // @formatter:on - } - - /** - * gh-3986 - */ - @Test - public void requestWhenDefaultsDisabledWithNoOverrideThenExcludesAllSecureHeaders() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithNoOverride")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(excludesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenDefaultsDisabledWithPlaceholderTrueThenExcludesAllSecureHeaders() throws Exception { - System.setProperty("security.headers.defaults.disabled", "true"); - this.spring.configLocations(this.xml("DefaultsDisabledWithPlaceholder")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(excludesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenDefaultsDisabledWithPlaceholderFalseThenIncludeAllSecureHeaders() throws Exception { - System.setProperty("security.headers.defaults.disabled", "false"); - this.spring.configLocations(this.xml("DefaultsDisabledWithPlaceholder")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(includesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenDefaultsDisabledWithPlaceholderMissingThenIncludeAllSecureHeaders() throws Exception { - System.clearProperty("security.headers.defaults.disabled"); - this.spring.configLocations(this.xml("DefaultsDisabledWithPlaceholder")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(includesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenUsingContentTypeOptionsThenDefaultsToNoSniff() throws Exception { - Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); - excludedHeaders.remove("X-Content-Type-Options"); - this.spring.configLocations(this.xml("DefaultsDisabledWithContentTypeOptions")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(header().string("X-Content-Type-Options", "nosniff")) - .andExpect(excludes(excludedHeaders)); - // @formatter:on - } - - @Test - public void requestWhenUsingFrameOptionsThenDefaultsToDeny() throws Exception { - Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); - excludedHeaders.remove("X-Frame-Options"); - this.spring.configLocations(this.xml("DefaultsDisabledWithFrameOptions")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(header().string("X-Frame-Options", "DENY")) - .andExpect(excludes(excludedHeaders)); - // @formatter:on - } - - @Test - public void requestWhenUsingFrameOptionsDenyThenRespondsWithDeny() throws Exception { - Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); - excludedHeaders.remove("X-Frame-Options"); - this.spring.configLocations(this.xml("DefaultsDisabledWithFrameOptionsDeny")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(header().string("X-Frame-Options", "DENY")) - .andExpect(excludes(excludedHeaders)); - // @formatter:on - } - - @Test - public void requestWhenUsingFrameOptionsSameOriginThenRespondsWithSameOrigin() throws Exception { - Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); - excludedHeaders.remove("X-Frame-Options"); - this.spring.configLocations(this.xml("DefaultsDisabledWithFrameOptionsSameOrigin")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(header().string("X-Frame-Options", "SAMEORIGIN")) - .andExpect(excludes(excludedHeaders)); - // @formatter:on - } - - @Test - public void configureWhenUsingFrameOptionsAllowFromNoOriginThenAutowireFails() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("DefaultsDisabledWithFrameOptionsAllowFromNoOrigin")) - .autowire()) - .withMessageContaining("Strategy requires a 'value' to be set."); - // FIXME better error message? - } - - @Test - public void configureWhenUsingFrameOptionsAllowFromBlankOriginThenAutowireFails() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy( - () -> this.spring.configLocations(this.xml("DefaultsDisabledWithFrameOptionsAllowFromBlankOrigin")) - .autowire()) - .withMessageContaining("Strategy requires a 'value' to be set."); - // FIXME better error message? - } - - @Test - public void requestWhenUsingFrameOptionsAllowFromThenRespondsWithAllowFrom() throws Exception { - Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); - excludedHeaders.remove("X-Frame-Options"); - this.spring.configLocations(this.xml("DefaultsDisabledWithFrameOptionsAllowFrom")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(header().string("X-Frame-Options", "ALLOW-FROM https://example.org")) - .andExpect(excludes(excludedHeaders)); - // @formatter:on - } - - @Test - public void requestWhenUsingFrameOptionsAllowFromWhitelistThenRespondsWithAllowFrom() throws Exception { - Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); - excludedHeaders.remove("X-Frame-Options"); - this.spring.configLocations(this.xml("DefaultsDisabledWithFrameOptionsAllowFromWhitelist")).autowire(); - // @formatter:off - this.mvc.perform(get("/").param("from", "https://example.org")) - .andExpect(status().isOk()) - .andExpect(header().string("X-Frame-Options", "ALLOW-FROM https://example.org")) - .andExpect(excludes(excludedHeaders)); - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(header().string("X-Frame-Options", "DENY")) - .andExpect(excludes(excludedHeaders)); - // @formatter:on - } - - @Test - public void requestWhenUsingCustomHeaderThenRespondsWithThatHeader() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithCustomHeader")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(header().string("a", "b")) - .andExpect(header().string("c", "d")) - .andExpect(excludesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenUsingCustomHeaderWriterThenRespondsWithThatHeader() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithCustomHeaderWriter")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(header().string("abc", "def")) - .andExpect(excludesDefaults()); - // @formatter:on - } - - @Test - public void configureWhenUsingCustomHeaderNameOnlyThenAutowireFails() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("DefaultsDisabledWithOnlyHeaderName")).autowire()); - } - - @Test - public void configureWhenUsingCustomHeaderValueOnlyThenAutowireFails() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("DefaultsDisabledWithOnlyHeaderValue")).autowire()); - } - - @Test - public void requestWhenPermissionsPolicyConfiguredWithGeolocationSelfThenGeolocationSelf() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithPermissionsPolicy")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(excludesDefaults()) - .andExpect(header().string("Permissions-Policy", "geolocation=(self)")); - // @formatter:on - } - - @Test - public void requestWhenUsingXssProtectionThenDefaultsToModeBlock() throws Exception { - Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); - excludedHeaders.remove("X-XSS-Protection"); - this.spring.configLocations(this.xml("DefaultsDisabledWithXssProtection")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(header().string("X-XSS-Protection", "0")) - .andExpect(excludes(excludedHeaders)); - // @formatter:on - } - - @Test - public void requestWhenSettingXssProtectionHeaderValueToZeroThenDefaultsToZero() throws Exception { - Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); - excludedHeaders.remove("X-XSS-Protection"); - this.spring.configLocations(this.xml("DefaultsDisabledWithXssProtectionHeaderValueZero")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(header().string("X-XSS-Protection", "0")) - .andExpect(excludes(excludedHeaders)); - // @formatter:on - } - - @Test - public void requestWhenSettingXssProtectionHeaderValueToOneThenDefaultsToOne() throws Exception { - Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); - excludedHeaders.remove("X-XSS-Protection"); - this.spring.configLocations(this.xml("DefaultsDisabledWithXssProtectionHeaderValueOne")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(header().string("X-XSS-Protection", "1")) - .andExpect(excludes(excludedHeaders)); - // @formatter:on - } - - @Test - public void requestWhenSettingXssProtectionHeaderValueToOneModeBlockThenDefaultsToOneModeBlock() throws Exception { - Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); - excludedHeaders.remove("X-XSS-Protection"); - this.spring.configLocations(this.xml("DefaultsDisabledWithXssProtectionHeaderValueOneModeBlock")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(header().string("X-XSS-Protection", "1; mode=block")) - .andExpect(excludes(excludedHeaders)); - // @formatter:on - } - - @Test - public void requestWhenUsingCacheControlThenRespondsWithCorrespondingHeaders() throws Exception { - Map includedHeaders = ImmutableMap.builder() - .put("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate") - .put("Expires", "0") - .put("Pragma", "no-cache") - .build(); - this.spring.configLocations(this.xml("DefaultsDisabledWithCacheControl")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(includes(includedHeaders)); - // @formatter:on - } - - @Test - public void requestWhenUsingHstsThenRespondsWithHstsHeader() throws Exception { - Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); - excludedHeaders.remove("Strict-Transport-Security"); - this.spring.configLocations(this.xml("DefaultsDisabledWithHsts")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(header().string("Strict-Transport-Security", "max-age=31536000 ; includeSubDomains")) - .andExpect(excludes(excludedHeaders)); - // @formatter:on - } - - @Test - public void insecureRequestWhenUsingHstsThenExcludesHstsHeader() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithHsts")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(excludesDefaults()); - // @formatter:on - } - - @Test - public void insecureRequestWhenUsingCustomHstsRequestMatcherThenIncludesHstsHeader() throws Exception { - Set excludedHeaders = new HashSet<>(defaultHeaders.keySet()); - excludedHeaders.remove("Strict-Transport-Security"); - this.spring.configLocations(this.xml("DefaultsDisabledWithCustomHstsRequestMatcher")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(header().string("Strict-Transport-Security", "max-age=1")) - .andExpect(excludes(excludedHeaders)); - // @formatter:on - } - - @Test - public void configureWhenUsingHpkpWithoutPinsThenAutowireFails() { - assertThatExceptionOfType(XmlBeanDefinitionStoreException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("DefaultsDisabledWithEmptyHpkp")).autowire()) - .havingRootCause() - .withMessageContaining("The content of element 'hpkp' is not complete"); - } - - @Test - public void configureWhenUsingHpkpWithEmptyPinsThenAutowireFails() { - assertThatExceptionOfType(XmlBeanDefinitionStoreException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("DefaultsDisabledWithEmptyPins")).autowire()) - .havingRootCause() - .withMessageContaining("The content of element 'pins' is not complete"); - } - - @Test - public void requestWhenUsingHpkpThenIncludesHpkpHeader() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithHpkp")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(header().string("Public-Key-Pins-Report-Only", - "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\"")) - .andExpect(excludesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenUsingHpkpDefaultsThenIncludesHpkpHeaderUsingSha256() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithHpkpDefaults")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(header().string("Public-Key-Pins-Report-Only", - "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\"")) - .andExpect(excludesDefaults()); - // @formatter:on - } - - @Test - public void insecureRequestWhenUsingHpkpThenExcludesHpkpHeader() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithHpkpDefaults")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(header().doesNotExist("Public-Key-Pins-Report-Only")) - .andExpect(excludesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenUsingHpkpCustomMaxAgeThenIncludesHpkpHeaderAccordingly() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithHpkpMaxAge")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(header().string("Public-Key-Pins-Report-Only", - "max-age=604800 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\"")) - .andExpect(excludesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenUsingHpkpReportThenIncludesHpkpHeaderAccordingly() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithHpkpReport")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(header().string("Public-Key-Pins", - "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\"")) - .andExpect(excludesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenUsingHpkpIncludeSubdomainsThenIncludesHpkpHeaderAccordingly() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithHpkpIncludeSubdomains")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(header().string("Public-Key-Pins-Report-Only", - "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; includeSubDomains")) - .andExpect(excludesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenUsingHpkpReportUriThenIncludesHpkpHeaderAccordingly() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithHpkpReportUri")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(header().string("Public-Key-Pins-Report-Only", - "max-age=5184000 ; pin-sha256=\"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=\" ; report-uri=\"https://example.net/pkp-report\"")) - .andExpect(excludesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenCacheControlDisabledThenExcludesHeader() throws Exception { - Collection cacheControl = Arrays.asList("Cache-Control", "Expires", "Pragma"); - Map allButCacheControl = remove(defaultHeaders, cacheControl); - this.spring.configLocations(this.xml("CacheControlDisabled")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(includes(allButCacheControl)) - .andExpect(excludes(cacheControl)); - // @formatter:on - } - - @Test - public void requestWhenContentTypeOptionsDisabledThenExcludesHeader() throws Exception { - Collection contentTypeOptions = Arrays.asList("X-Content-Type-Options"); - Map allButContentTypeOptions = remove(defaultHeaders, contentTypeOptions); - this.spring.configLocations(this.xml("ContentTypeOptionsDisabled")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(includes(allButContentTypeOptions)) - .andExpect(excludes(contentTypeOptions)); - // @formatter:on - } - - @Test - public void requestWhenHstsDisabledThenExcludesHeader() throws Exception { - Collection hsts = Arrays.asList("Strict-Transport-Security"); - Map allButHsts = remove(defaultHeaders, hsts); - this.spring.configLocations(this.xml("HstsDisabled")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(includes(allButHsts)) - .andExpect(excludes(hsts)); - // @formatter:on - } - - @Test - public void requestWhenHpkpDisabledThenExcludesHeader() throws Exception { - this.spring.configLocations(this.xml("HpkpDisabled")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(includesDefaults()); - // @formatter:on - } - - @Test - public void requestWhenFrameOptionsDisabledThenExcludesHeader() throws Exception { - Collection frameOptions = Arrays.asList("X-Frame-Options"); - Map allButFrameOptions = remove(defaultHeaders, frameOptions); - this.spring.configLocations(this.xml("FrameOptionsDisabled")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(includes(allButFrameOptions)) - .andExpect(excludes(frameOptions)); - // @formatter:on - } - - @Test - public void requestWhenXssProtectionDisabledThenExcludesHeader() throws Exception { - Collection xssProtection = Arrays.asList("X-XSS-Protection"); - Map allButXssProtection = remove(defaultHeaders, xssProtection); - this.spring.configLocations(this.xml("XssProtectionDisabled")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(includes(allButXssProtection)) - .andExpect(excludes(xssProtection)); - // @formatter:on - } - - @Test - public void configureWhenHstsDisabledAndIncludeSubdomainsSpecifiedThenAutowireFails() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy( - () -> this.spring.configLocations(this.xml("HstsDisabledSpecifyingIncludeSubdomains")).autowire()) - .withMessageContaining("include-subdomains"); - } - - @Test - public void configureWhenHstsDisabledAndMaxAgeSpecifiedThenAutowireFails() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("HstsDisabledSpecifyingMaxAge")).autowire()) - .withMessageContaining("max-age"); - } - - @Test - public void configureWhenHstsDisabledAndRequestMatcherSpecifiedThenAutowireFails() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("HstsDisabledSpecifyingRequestMatcher")).autowire()) - .withMessageContaining("request-matcher-ref"); - } - - @Test - public void configureWhenXssProtectionDisabledAndHeaderValueSpecifiedThenAutowireFails() { - assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy( - () -> this.spring.configLocations(this.xml("XssProtectionDisabledSpecifyingHeaderValue")).autowire()) - .withMessageContaining("header-value"); - } - - @Test - public void configureWhenFrameOptionsDisabledAndPolicySpecifiedThenAutowireFails() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("FrameOptionsDisabledSpecifyingPolicy")).autowire()) - .withMessageContaining("policy"); - } - - @Test - public void requestWhenContentSecurityPolicyDirectivesConfiguredThenIncludesDirectives() throws Exception { - Map includedHeaders = new HashMap<>(defaultHeaders); - includedHeaders.put("Content-Security-Policy", "default-src 'self'"); - this.spring.configLocations(this.xml("ContentSecurityPolicyWithPolicyDirectives")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(includes(includedHeaders)); - // @formatter:on - } - - @Test - public void requestWhenHeadersDisabledAndContentSecurityPolicyConfiguredThenExcludesHeader() throws Exception { - this.spring.configLocations(this.xml("HeadersDisabledWithContentSecurityPolicy")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(excludesDefaults()) - .andExpect(excludes("Content-Security-Policy")); - // @formatter:on - } - - @Test - public void requestWhenDefaultsDisabledAndContentSecurityPolicyConfiguredThenIncludesHeader() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithContentSecurityPolicy")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(excludesDefaults()) - .andExpect(header().string("Content-Security-Policy", "default-src 'self'")); - // @formatter:on - } - - @Test - public void configureWhenContentSecurityPolicyConfiguredWithEmptyDirectivesThenAutowireFails() { - assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy( - () -> this.spring.configLocations(this.xml("ContentSecurityPolicyWithEmptyDirectives")).autowire()); - } - - @Test - public void requestWhenContentSecurityPolicyConfiguredWithReportOnlyThenIncludesReportOnlyHeader() - throws Exception { - Map includedHeaders = new HashMap<>(defaultHeaders); - includedHeaders.put("Content-Security-Policy-Report-Only", - "default-src https:; report-uri https://example.org/"); - this.spring.configLocations(this.xml("ContentSecurityPolicyWithReportOnly")).autowire(); - // @formatter:off - this.mvc.perform(get("/").secure(true)) - .andExpect(status().isOk()) - .andExpect(includes(includedHeaders)); - // @formatter:on - } - - @Test - public void requestWhenReferrerPolicyConfiguredThenResponseDefaultsToNoReferrer() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithReferrerPolicy")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(excludesDefaults()) - .andExpect(header().string("Referrer-Policy", "no-referrer")); - // @formatter:on - } - - @Test - public void requestWhenReferrerPolicyConfiguredWithSameOriginThenRespondsWithSameOrigin() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithReferrerPolicySameOrigin")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(excludesDefaults()) - .andExpect(header().string("Referrer-Policy", "same-origin")); - // @formatter:on - } - - @Test - public void requestWhenCrossOriginOpenerPolicyWithSameOriginAllowPopupsThenRespondsWithSameOriginAllowPopups() - throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginOpenerPolicy")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(excludesDefaults()) - .andExpect(header().string("Cross-Origin-Opener-Policy", "same-origin-allow-popups")); - // @formatter:on - } - - @Test - public void requestWhenCrossOriginEmbedderPolicyWithRequireCorpThenRespondsWithRequireCorp() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginEmbedderPolicy")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(excludesDefaults()) - .andExpect(header().string("Cross-Origin-Embedder-Policy", "require-corp")); - // @formatter:on - } - - @Test - public void requestWhenCrossOriginResourcePolicyWithSameOriginThenRespondsWithSameOrigin() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginResourcePolicy")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(excludesDefaults()) - .andExpect(header().string("Cross-Origin-Resource-Policy", "same-origin")); - // @formatter:on - } - - @Test - public void requestWhenCrossOriginPoliciesRespondsCrossOriginPolicies() throws Exception { - this.spring.configLocations(this.xml("DefaultsDisabledWithCrossOriginPolicies")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isOk()) - .andExpect(excludesDefaults()) - .andExpect(header().string("Cross-Origin-Opener-Policy", "same-origin")) - .andExpect(header().string("Cross-Origin-Embedder-Policy", "require-corp")) - .andExpect(header().string("Cross-Origin-Resource-Policy", "same-origin")); - // @formatter:on - } - - @Test - public void requestWhenSessionManagementConcurrencyControlMaxSessionIsOne() throws Exception { - System.setProperty("security.session-management.concurrency-control.max-sessions", "1"); - this.spring.configLocations(this.xml("DefaultsSessionManagementConcurrencyControlMaxSessions")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder requestBuilder = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password"); - HttpSession firstSession = this.mvc.perform(requestBuilder) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/")) - .andReturn() - .getRequest() - .getSession(false); - // @formatter:on - assertThat(firstSession).isNotNull(); - // @formatter:off - this.mvc.perform(requestBuilder) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?error")); - // @formatter:on - } - - @Test - public void requestWhenSessionManagementConcurrencyControlMaxSessionIsUnlimited() throws Exception { - System.setProperty("security.session-management.concurrency-control.max-sessions", "-1"); - this.spring.configLocations(this.xml("DefaultsSessionManagementConcurrencyControlMaxSessions")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder requestBuilder = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password"); - HttpSession firstSession = this.mvc.perform(requestBuilder) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/")) - .andReturn() - .getRequest() - .getSession(false); - assertThat(firstSession).isNotNull(); - HttpSession secondSession = this.mvc.perform(requestBuilder) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/")) - .andReturn() - .getRequest() - .getSession(false); - assertThat(secondSession).isNotNull(); - // @formatter:on - assertThat(firstSession.getId()).isNotEqualTo(secondSession.getId()); - } - - @Test - public void requestWhenSessionManagementConcurrencyControlMaxSessionRefIsOneForNonAdminUsers() throws Exception { - this.spring.configLocations(this.xml("DefaultsSessionManagementConcurrencyControlMaxSessionsRef")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder requestBuilder = post("/login") - .with(csrf()) - .param("username", "user") - .param("password", "password"); - HttpSession firstSession = this.mvc.perform(requestBuilder) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/")) - .andReturn() - .getRequest() - .getSession(false); - // @formatter:on - assertThat(firstSession).isNotNull(); - // @formatter:off - this.mvc.perform(requestBuilder) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?error")); - // @formatter:on - } - - @Test - public void requestWhenSessionManagementConcurrencyControlMaxSessionRefIsTwoForAdminUsers() throws Exception { - this.spring.configLocations(this.xml("DefaultsSessionManagementConcurrencyControlMaxSessionsRef")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder requestBuilder = post("/login") - .with(csrf()) - .param("username", "admin") - .param("password", "password"); - HttpSession firstSession = this.mvc.perform(requestBuilder) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/")) - .andReturn() - .getRequest() - .getSession(false); - assertThat(firstSession).isNotNull(); - HttpSession secondSession = this.mvc.perform(requestBuilder) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/")) - .andReturn() - .getRequest() - .getSession(false); - assertThat(secondSession).isNotNull(); - // @formatter:on - assertThat(firstSession.getId()).isNotEqualTo(secondSession.getId()); - // @formatter:off - this.mvc.perform(requestBuilder) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?error")); - // @formatter:on - } - - @Test - public void requestWhenSessionManagementConcurrencyControlWithInvalidMaxSessionConfig() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring - .configLocations(this.xml("DefaultsSessionManagementConcurrencyControlWithInvalidMaxSessionsConfig")) - .autowire()) - .withMessageContaining("Cannot use 'max-sessions' attribute and 'max-sessions-ref' attribute together."); - } - - private static ResultMatcher includesDefaults() { - return includes(defaultHeaders); - } - - private static ResultMatcher includes(Map headers) { - return (result) -> { - for (Map.Entry header : headers.entrySet()) { - header().string(header.getKey(), header.getValue()).match(result); - } - }; - } - - private static ResultMatcher excludesDefaults() { - return excludes(defaultHeaders.keySet()); - } - - private static ResultMatcher excludes(Collection headers) { - return (result) -> { - for (String name : headers) { - header().doesNotExist(name).match(result); - } - }; - } - - private static ResultMatcher excludes(String... headers) { - return excludes(Arrays.asList(headers)); - } - - private static Map remove(Map map, Collection keys) { - Map copy = new HashMap<>(map); - for (K key : keys) { - copy.remove(key); - } - return copy; - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - @RestController - public static class SimpleController { - - @GetMapping("/") - public String ok() { - return "ok"; - } - - } - - public static class CustomSessionLimit implements SessionLimit { - - @Override - public Integer apply(Authentication authentication) { - if ("admin".equals(authentication.getName())) { - return 2; - } - return 1; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/HttpInterceptUrlTests.java b/config/src/test/java/org/springframework/security/config/http/HttpInterceptUrlTests.java deleted file mode 100644 index b166fed64e2..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/HttpInterceptUrlTests.java +++ /dev/null @@ -1,86 +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.http; - -import jakarta.servlet.Filter; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import org.springframework.mock.web.MockServletContext; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.context.ConfigurableWebApplicationContext; -import org.springframework.web.context.support.XmlWebApplicationContext; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -public class HttpInterceptUrlTests { - - ConfigurableWebApplicationContext context; - - MockMvc mockMvc; - - @AfterEach - public void close() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void interceptUrlWhenRequestMatcherRefThenWorks() throws Exception { - loadConfig("interceptUrlWhenRequestMatcherRefThenWorks.xml"); - // @formatter:off - this.mockMvc.perform(get("/foo")) - .andExpect(status().isUnauthorized()); - this.mockMvc.perform(get("/FOO")) - .andExpect(status().isUnauthorized()); - this.mockMvc.perform(get("/other")) - .andExpect(status().isOk()); - // @formatter:on - } - - private void loadConfig(String... configLocations) { - for (int i = 0; i < configLocations.length; i++) { - configLocations[i] = getClass().getName().replaceAll("\\.", "/") + "-" + configLocations[i]; - } - XmlWebApplicationContext context = new XmlWebApplicationContext(); - context.setConfigLocations(configLocations); - context.setServletContext(new MockServletContext()); - context.refresh(); - this.context = context; - context.getAutowireCapableBeanFactory().autowireBean(this); - Filter springSecurityFilterChain = context.getBean("springSecurityFilterChain", Filter.class); - this.mockMvc = MockMvcBuilders.standaloneSetup(new FooController()) - .addFilters(springSecurityFilterChain) - .build(); - } - - @RestController - static class FooController { - - @GetMapping("/*") - String foo() { - return "foo"; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/InterceptUrlConfigTests.java b/config/src/test/java/org/springframework/security/config/http/InterceptUrlConfigTests.java deleted file mode 100644 index fab1e932c01..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/InterceptUrlConfigTests.java +++ /dev/null @@ -1,395 +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.http; - -import java.util.Collections; -import java.util.Map; - -import jakarta.servlet.DispatcherType; -import jakarta.servlet.ServletRegistration; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.stubbing.Answer; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.mock.web.MockServletContext; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.RequestPostProcessor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.util.WebUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.assertj.core.api.Assertions.assertThatNoException; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Rob Winch - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class InterceptUrlConfigTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/InterceptUrlConfigTests"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - /** - * sec-2256 - */ - @Test - public void requestWhenMethodIsSpecifiedThenItIsNotGivenPriority() throws Exception { - this.spring.configLocations(this.xml("Sec2256")).autowire(); - // @formatter:off - this.mvc.perform(post("/path").with(userCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(get("/path").with(userCredentials())) - .andExpect(status().isOk()); - // @formatter:on - } - - /** - * sec-2256 - */ - @Test - public void requestWhenMethodIsSpecifiedAndAuthorizationManagerThenItIsNotGivenPriority() throws Exception { - this.spring.configLocations(this.xml("Sec2256AuthorizationManager")).autowire(); - // @formatter:off - this.mvc.perform(post("/path").with(userCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(get("/path").with(userCredentials())) - .andExpect(status().isOk()); - // @formatter:on - assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); - } - - /** - * sec-2355 - */ - @Test - public void requestWhenUsingPatchThenAuthorizesRequestsAccordingly() throws Exception { - this.spring.configLocations(this.xml("PatchMethod")).autowire(); - // @formatter:off - this.mvc.perform(get("/path").with(userCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(patch("/path").with(userCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(patch("/path").with(adminCredentials())) - .andExpect(status().isOk()); - // @formatter:on - } - - /** - * sec-2355 - */ - @Test - public void requestWhenUsingPatchAndAuthorizationManagerThenAuthorizesRequestsAccordingly() throws Exception { - this.spring.configLocations(this.xml("PatchMethodAuthorizationManager")).autowire(); - // @formatter:off - this.mvc.perform(get("/path").with(userCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(patch("/path").with(userCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(patch("/path").with(adminCredentials())) - .andExpect(status().isOk()); - // @formatter:on - assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); - } - - @Test - public void requestWhenUsingHasAnyRoleThenAuthorizesRequestsAccordingly() throws Exception { - this.spring.configLocations(this.xml("HasAnyRole")).autowire(); - // @formatter:off - this.mvc.perform(get("/path").with(userCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(get("/path").with(adminCredentials())) - .andExpect(status().isForbidden()); - // @formatter:on - } - - @Test - public void requestWhenUsingHasAnyRoleAndAuthorizationManagerThenAuthorizesRequestsAccordingly() throws Exception { - this.spring.configLocations(this.xml("HasAnyRoleAuthorizationManager")).autowire(); - // @formatter:off - this.mvc.perform(get("/path").with(userCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(get("/path").with(adminCredentials())) - .andExpect(status().isForbidden()); - // @formatter:on - assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); - } - - /** - * sec-2059 - */ - @Test - public void requestWhenUsingPathVariablesThenAuthorizesRequestsAccordingly() throws Exception { - this.spring.configLocations(this.xml("PathVariables")).autowire(); - // @formatter:off - this.mvc.perform(get("/path/user/path").with(userCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(get("/path/otheruser/path").with(userCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(get("/path").with(userCredentials())) - .andExpect(status().isForbidden()); - // @formatter:on - } - - /** - * sec-2059 - */ - @Test - public void requestWhenUsingPathVariablesAndAuthorizationManagerThenAuthorizesRequestsAccordingly() - throws Exception { - this.spring.configLocations(this.xml("PathVariablesAuthorizationManager")).autowire(); - // @formatter:off - this.mvc.perform(get("/path/user/path").with(userCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(get("/path/otheruser/path").with(userCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(get("/path").with(userCredentials())) - .andExpect(status().isForbidden()); - // @formatter:on - assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); - } - - /** - * gh-3786 - */ - @Test - public void requestWhenUsingCamelCasePathVariablesThenAuthorizesRequestsAccordingly() throws Exception { - this.spring.configLocations(this.xml("CamelCasePathVariables")).autowire(); - // @formatter:off - this.mvc.perform(get("/path/user/path").with(userCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(get("/path/otheruser/path").with(userCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(get("/PATH/user/path").with(userCredentials())) - .andExpect(status().isForbidden()); - // @formatter:on - } - - /** - * gh-3786 - */ - @Test - public void requestWhenUsingCamelCasePathVariablesAndAuthorizationManagerThenAuthorizesRequestsAccordingly() - throws Exception { - this.spring.configLocations(this.xml("CamelCasePathVariablesAuthorizationManager")).autowire(); - // @formatter:off - this.mvc.perform(get("/path/user/path").with(userCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(get("/path/otheruser/path").with(userCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(get("/PATH/user/path").with(userCredentials())) - .andExpect(status().isForbidden()); - // @formatter:on - assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); - } - - /** - * sec-2059 - */ - @Test - public void requestWhenUsingPathVariablesAndTypeConversionThenAuthorizesRequestsAccordingly() throws Exception { - this.spring.configLocations(this.xml("TypeConversionPathVariables")).autowire(); - // @formatter:off - this.mvc.perform(get("/path/1/path").with(userCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(get("/path/2/path").with(userCredentials())) - .andExpect(status().isForbidden()); - // @formatter:on - } - - /** - * sec-2059 - */ - @Test - public void requestWhenUsingPathVariablesAndTypeConversionAndAuthorizationManagerThenAuthorizesRequestsAccordingly() - throws Exception { - this.spring.configLocations(this.xml("TypeConversionPathVariablesAuthorizationManager")).autowire(); - // @formatter:off - this.mvc.perform(get("/path/1/path").with(userCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(get("/path/2/path").with(userCredentials())) - .andExpect(status().isForbidden()); - // @formatter:on - assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); - } - - @Test - public void requestWhenUsingMvcMatchersAndPathVariablesThenAuthorizesRequestsAccordingly() throws Exception { - this.spring.configLocations(this.xml("MvcMatchersPathVariables")).autowire(); - // @formatter:off - this.mvc.perform(get("/path/user/path").with(userCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(get("/path/otheruser/path").with(userCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(get("/PATH/user/path").with(userCredentials())) - .andExpect(status().isForbidden()); - // @formatter:on - } - - @Test - public void requestWhenUsingMvcMatchersAndPathVariablesAndAuthorizationManagerThenAuthorizesRequestsAccordingly() - throws Exception { - this.spring.configLocations(this.xml("MvcMatchersPathVariablesAuthorizationManager")).autowire(); - // @formatter:off - this.mvc.perform(get("/path/user/path").with(userCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(get("/path/otheruser/path").with(userCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(get("/PATH/user/path").with(userCredentials())) - .andExpect(status().isForbidden()); - // @formatter:on - assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); - } - - @Test - public void configureWhenUsingRegexMatcherAndServletPathThenThrowsException() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("RegexMatcherServletPath")).autowire()); - } - - @Test - public void configureWhenUsingRegexMatcherAndServletPathAndAuthorizationManagerThenThrowsException() { - assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy( - () -> this.spring.configLocations(this.xml("RegexMatcherServletPathAuthorizationManager")).autowire()); - } - - @Test - public void configureWhenUsingCiRegexMatcherAndServletPathThenThrowsException() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("CiRegexMatcherServletPath")).autowire()); - } - - @Test - public void configureWhenUsingCiRegexMatcherAndServletPathAndAuthorizationManagerThenThrowsException() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("CiRegexMatcherServletPathAuthorizationManager")) - .autowire()); - } - - @Test - public void configureWhenUsingDefaultMatcherAndServletPathThenNoException() { - assertThatNoException() - .isThrownBy(() -> this.spring.configLocations(this.xml("DefaultMatcherServletPath")).autowire()); - } - - @Test - public void configureWhenUsingDefaultMatcherAndServletPathAndAuthorizationManagerThenNoException() { - assertThatNoException() - .isThrownBy(() -> this.spring.configLocations(this.xml("DefaultMatcherServletPathAuthorizationManager")) - .autowire()); - } - - @Test - public void requestWhenUsingFilterAllDispatcherTypesAndAuthorizationManagerThenAuthorizesRequestsAccordingly() - throws Exception { - this.spring.configLocations(this.xml("AuthorizationManagerFilterAllDispatcherTypes")).autowire(); - // @formatter:off - this.mvc.perform(get("/path").with(userCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(get("/path").with(adminCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(get("/error").with((request) -> { - request.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/error"); - request.setDispatcherType(DispatcherType.ERROR); - return request; - })).andExpect(status().isOk()); - this.mvc.perform(get("/path").with((request) -> { - request.setAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE, "/path"); - request.setDispatcherType(DispatcherType.ERROR); - return request; - })).andExpect(status().isUnauthorized()); - // @formatter:on - assertThat(this.spring.getContext().getBean(AuthorizationManager.class)).isNotNull(); - } - - private static RequestPostProcessor adminCredentials() { - return httpBasic("admin", "password"); - } - - private static RequestPostProcessor userCredentials() { - return httpBasic("user", "password"); - } - - private MockServletContext mockServletContext(String servletPath) { - MockServletContext servletContext = spy(new MockServletContext()); - final ServletRegistration registration = mock(ServletRegistration.class); - given(registration.getMappings()).willReturn(Collections.singleton(servletPath)); - Answer> answer = (invocation) -> Collections.singletonMap("spring", - registration); - given(servletContext.getServletRegistrations()).willAnswer(answer); - return servletContext; - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - @RestController - static class PathController { - - @RequestMapping("/path") - String path() { - return "path"; - } - - @RequestMapping("/path/{un}/path") - String path(@PathVariable("un") String name) { - return name; - } - - } - - @RestController - static class ErrorController { - - @GetMapping("/error") - String error() { - return "error"; - } - - } - - public static class Id { - - public boolean isOne(int i) { - return i == 1; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java b/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java deleted file mode 100644 index 8a4a15821ac..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/MiscHttpConfigTests.java +++ /dev/null @@ -1,1078 +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.http; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.security.AccessController; -import java.security.Principal; -import java.security.cert.X509Certificate; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.stream.Collectors; - -import javax.security.auth.Subject; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.spi.LoginModule; - -import ch.qos.logback.classic.Logger; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.Appender; -import jakarta.servlet.Filter; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpServletResponseWrapper; -import org.apache.http.HttpStatus; -import org.assertj.core.api.iterable.Extractor; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.stubbing.Answer; -import org.slf4j.LoggerFactory; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.lang.NonNull; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.BeanNameCollectingPostProcessor; -import org.springframework.security.access.AccessDecisionManager; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.PermissionEvaluator; -import org.springframework.security.authentication.AnonymousAuthenticationToken; -import org.springframework.security.authentication.AuthenticationDetailsSource; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.InsufficientAuthenticationException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authentication.jaas.AuthorityGranter; -import org.springframework.security.config.TestDeferredSecurityContext; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.security.test.web.servlet.RequestCacheResultMatcher; -import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.access.ExceptionTranslationFilter; -import org.springframework.security.web.access.channel.ChannelProcessingFilter; -import org.springframework.security.web.access.intercept.AuthorizationFilter; -import org.springframework.security.web.access.intercept.FilterSecurityInterceptor; -import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.authentication.logout.LogoutFilter; -import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; -import org.springframework.security.web.authentication.preauth.x509.X509TestUtils; -import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; -import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter; -import org.springframework.security.web.authentication.ui.DefaultResourcesFilter; -import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; -import org.springframework.security.web.context.HttpRequestResponseHolder; -import org.springframework.security.web.context.SecurityContextHolderFilter; -import org.springframework.security.web.context.SecurityContextRepository; -import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter; -import org.springframework.security.web.csrf.CsrfFilter; -import org.springframework.security.web.firewall.FirewalledRequest; -import org.springframework.security.web.firewall.HttpFirewall; -import org.springframework.security.web.firewall.RequestRejectedException; -import org.springframework.security.web.firewall.RequestRejectedHandler; -import org.springframework.security.web.header.HeaderWriterFilter; -import org.springframework.security.web.savedrequest.RequestCache; -import org.springframework.security.web.savedrequest.RequestCacheAwareFilter; -import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; -import org.springframework.security.web.session.DisableEncodeUrlFilter; -import org.springframework.security.web.transport.HttpsRedirectFilter; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.test.web.servlet.request.RequestPostProcessor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.context.support.XmlWebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.BDDMockito.willAnswer; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Luke Taylor - * @author Rob Winch - */ -@ExtendWith(SpringTestContextExtension.class) -public class MiscHttpConfigTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/MiscHttpConfigTests"; - - @Autowired - MockMvc mvc; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - public void configureWhenUsingMinimalConfigurationThenParses() { - this.spring.configLocations(xml("MinimalConfiguration")).autowire(); - } - - @Test - public void configureWhenUsingAutoConfigThenSetsUpCorrectFilterList() { - this.spring.configLocations(xml("AutoConfig")).autowire(); - assertThatFiltersMatchExpectedAutoConfigList(); - } - - @Test - public void configureWhenUsingSecurityNoneThenNoFiltersAreSetUp() { - this.spring.configLocations(xml("NoSecurityForPattern")).autowire(); - assertThat(getFilters("/unprotected")).isEmpty(); - } - - @Test - public void requestWhenUsingDebugFilterAndPatternIsNotConfigureForSecurityThenRespondsOk() throws Exception { - this.spring.configLocations(xml("NoSecurityForPattern")).autowire(); - // @formatter:off - this.mvc.perform(get("/unprotected")) - .andExpect(status().isNotFound()); - this.mvc.perform(get("/nomatch")) - .andExpect(status().isNotFound()); - // @formatter:on - } - - @Test - public void requestWhenHttpPatternUsesRegexMatchingThenMatchesAccordingly() throws Exception { - this.spring.configLocations(xml("RegexSecurityPattern")).autowire(); - // @formatter:off - this.mvc.perform(get("/protected")) - .andExpect(status().isUnauthorized()); - this.mvc.perform(get("/unprotected")) - .andExpect(status().isNotFound()); - // @formatter:on - } - - @Test - public void requestWhenHttpPatternUsesCiRegexMatchingThenMatchesAccordingly() throws Exception { - this.spring.configLocations(xml("CiRegexSecurityPattern")).autowire(); - // @formatter:off - this.mvc.perform(get("/ProTectEd")) - .andExpect(status().isUnauthorized()); - this.mvc.perform(get("/UnProTectEd")) - .andExpect(status().isNotFound()); - // @formatter:on - } - - @Test - public void requestWhenHttpPatternUsesCustomRequestMatcherThenMatchesAccordingly() throws Exception { - this.spring.configLocations(xml("CustomRequestMatcher")).autowire(); - // @formatter:off - this.mvc.perform(get("/protected")) - .andExpect(status().isUnauthorized()); - this.mvc.perform(get("/unprotected")) - .andExpect(status().isNotFound()); - // @formatter:on - } - - /** - * SEC-1152 - */ - @Test - public void requestWhenUsingMinimalConfigurationThenHonorsAnonymousEndpoints() throws Exception { - this.spring.configLocations(xml("AnonymousEndpoints")).autowire(); - // @formatter:off - this.mvc.perform(get("/protected")) - .andExpect(status().isUnauthorized()); - this.mvc.perform(get("/unprotected")) - .andExpect(status().isNotFound()); - // @formatter:on - assertThat(getFilter(AnonymousAuthenticationFilter.class)).isNotNull(); - } - - @Test - public void requestWhenAnonymousIsDisabledThenRejectsAnonymousEndpoints() throws Exception { - this.spring.configLocations(xml("AnonymousDisabled")).autowire(); - // @formatter:off - this.mvc.perform(get("/protected")) - .andExpect(status().isUnauthorized()); - this.mvc.perform(get("/unprotected")) - .andExpect(status().isUnauthorized()); - // @formatter:on - assertThat(getFilter(AnonymousAuthenticationFilter.class)).isNull(); - } - - @Test - public void requestWhenAnonymousUsesCustomAttributesThenRespondsWithThoseAttributes() throws Exception { - this.spring.configLocations(xml("AnonymousCustomAttributes")).autowire(); - // @formatter:off - this.mvc.perform(get("/protected").with(userCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(get("/protected")) - .andExpect(status().isOk()) - .andExpect(content().string("josh")); - this.mvc.perform(get("/customKey")) - .andExpect(status().isOk()) - .andExpect(content().string(String.valueOf("myCustomKey".hashCode()))); - // @formatter:on - } - - @Test - public void requestWhenAnonymousUsesMultipleGrantedAuthoritiesThenRespondsWithThoseAttributes() throws Exception { - this.spring.configLocations(xml("AnonymousMultipleAuthorities")).autowire(); - // @formatter:off - this.mvc.perform(get("/protected").with(userCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(get("/protected")) - .andExpect(status().isOk()) - .andExpect(content().string("josh")); - this.mvc.perform(get("/customKey")) - .andExpect(status().isOk()) - .andExpect(content().string(String.valueOf("myCustomKey".hashCode()))); - // @formatter:on - } - - @Test - public void requestWhenInterceptUrlMatchesMethodThenSecuresAccordingly() throws Exception { - this.spring.configLocations(xml("InterceptUrlMethod")).autowire(); - // @formatter:off - this.mvc.perform(get("/protected").with(userCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(post("/protected").with(userCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(post("/protected").with(postCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(delete("/protected").with(postCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(delete("/protected").with(adminCredentials())) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void requestWhenInterceptUrlMatchesMethodAndRequiresHttpsThenSecuresAccordingly() throws Exception { - this.spring.configLocations(xml("InterceptUrlMethodRequiresHttps")).autowire(); - // @formatter:off - this.mvc.perform(post("/protected").with(csrf())) - .andExpect(status().isOk()); - this.mvc.perform(get("/protected").secure(true).with(userCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(get("/protected").secure(true).with(adminCredentials())) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void requestWhenInterceptUrlMatchesAnyPatternAndRequiresHttpsThenSecuresAccordingly() throws Exception { - this.spring.configLocations(xml("InterceptUrlMethodRequiresHttpsAny")).autowire(); - // @formatter:off - this.mvc.perform(post("/protected").with(csrf())) - .andExpect(status().isOk()); - this.mvc.perform(get("/protected").secure(true).with(userCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(get("/protected").secure(true).with(adminCredentials())) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void configureWhenOncePerRequestIsFalseThenFilterSecurityInterceptorExercisedForForwards() { - this.spring.configLocations(xml("OncePerRequest")).autowire(); - FilterSecurityInterceptor filterSecurityInterceptor = getFilter(FilterSecurityInterceptor.class); - assertThat(filterSecurityInterceptor.isObserveOncePerRequest()).isFalse(); - } - - @Test - public void configureWhenOncePerRequestIsTrueThenFilterSecurityInterceptorObserveOncePerRequestIsTrue() { - this.spring.configLocations(xml("OncePerRequestTrue")).autowire(); - FilterSecurityInterceptor filterSecurityInterceptor = getFilter(FilterSecurityInterceptor.class); - assertThat(filterSecurityInterceptor.isObserveOncePerRequest()).isTrue(); - } - - @Test - public void requestWhenCustomHttpBasicEntryPointRefThenInvokesOnCommence() throws Exception { - this.spring.configLocations(xml("CustomHttpBasicEntryPointRef")).autowire(); - AuthenticationEntryPoint entryPoint = this.spring.getContext().getBean(AuthenticationEntryPoint.class); - // @formatter:off - this.mvc.perform(get("/protected")) - .andExpect(status().isOk()); - // @formatter:on - verify(entryPoint).commence(any(HttpServletRequest.class), any(HttpServletResponse.class), - any(AuthenticationException.class)); - } - - @Test - public void configureWhenInterceptUrlWithRequiresChannelThenAddedChannelFilterToChain() { - this.spring.configLocations(xml("InterceptUrlMethodRequiresHttpsAny")).autowire(); - assertThat(getFilter(ChannelProcessingFilter.class)).isNotNull(); - } - - @Test - public void configureWhenRedirectToHttpsThenFilterAdded() { - this.spring.configLocations(xml("RedirectToHttpsRequiresHttpsAny")).autowire(); - assertThat(getFilter(HttpsRedirectFilter.class)).isNotNull(); - } - - @Test - public void getWhenRedirectToHttpsAnyThenRedirects() throws Exception { - this.spring.configLocations(xml("RedirectToHttpsRequiresHttpsAny")).autowire(); - // @formatter:off - this.mvc.perform(get("http://localhost")) - .andExpect(redirectedUrl("https://localhost")); - // @formatter:on - } - - @Test - public void getWhenPortsMappedThenRedirectedAccordingly() throws Exception { - this.spring.configLocations(xml("PortsMappedInterceptUrlMethodRequiresAny")).autowire(); - // @formatter:off - this.mvc.perform(get("http://localhost:9080/protected")) - .andExpect(redirectedUrl("https://localhost:9443/protected")); - // @formatter:on - } - - @Test - public void configureWhenCustomFiltersThenAddedToChainInCorrectOrder() { - System.setProperty("customFilterRef", "userFilter"); - this.spring.configLocations(xml("CustomFilters")).autowire(); - List filters = getFilters("/"); - Class userFilterClass = this.spring.getContext().getBean("userFilter").getClass(); - assertThat(filters).extracting((Extractor>) (filter) -> filter.getClass()) - .containsSubsequence(userFilterClass, userFilterClass, SecurityContextHolderFilter.class, - LogoutFilter.class, userFilterClass); - } - - @Test - public void configureWhenTwoFiltersWithSameOrderThenException() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(xml("CollidingFilters")).autowire()); - } - - @Test - public void configureWhenUsingX509ThenAddsX509FilterCorrectly() { - this.spring.configLocations(xml("X509")).autowire(); - assertThat(getFilters("/")).extracting((Extractor>) (filter) -> filter.getClass()) - .containsSubsequence(CsrfFilter.class, X509AuthenticationFilter.class, ExceptionTranslationFilter.class); - } - - @Test - public void getWhenUsingX509PrincipalExtractorRef() throws Exception { - this.spring.configLocations(xml("X509PrincipalExtractorRef")).autowire(); - X509Certificate certificate = X509TestUtils.buildTestCertificate(); - RequestPostProcessor x509 = x509(certificate); - // @formatter:off - this.mvc.perform(get("/protected").with(x509)) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void getWhenUsingX509PrincipalExtractorRefAndSubjectPrincipalRegex() throws Exception { - String xmlResourceName = "X509PrincipalExtractorRefAndSubjectPrincipalRegex"; - // @formatter:off - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(xml(xmlResourceName)).autowire()) - .withMessage("Configuration problem: The attribute 'principal-extractor-ref' cannot be used together with the 'subject-principal-regex' attribute within \n" + "Offending resource: class path resource [org/springframework/security/config/http/MiscHttpConfigTests-X509PrincipalExtractorRefAndSubjectPrincipalRegex.xml]"); - // @formatter:on - } - - @Test - public void getWhenUsingX509AndPropertyPlaceholderThenSubjectPrincipalRegexIsConfigured() throws Exception { - System.setProperty("subject_principal_regex", "OU=(.*?)(?:,|$)"); - this.spring.configLocations(xml("X509")).autowire(); - RequestPostProcessor x509 = x509( - "classpath:org/springframework/security/config/http/MiscHttpConfigTests-certificate.pem"); - // @formatter:off - this.mvc.perform(get("/protected").with(x509)) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void getWhenUsingX509CustomSecurityContextHolderStrategyThenUses() throws Exception { - System.setProperty("subject_principal_regex", "OU=(.*?)(?:,|$)"); - this.spring.configLocations(xml("X509WithSecurityContextHolderStrategy")).autowire(); - RequestPostProcessor x509 = x509( - "classpath:org/springframework/security/config/http/MiscHttpConfigTests-certificate.pem"); - // @formatter:off - this.mvc.perform(get("/protected").with(x509)) - .andExpect(status().isOk()); - // @formatter:on - verify(this.spring.getContext().getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); - } - - @Test - public void configureWhenUsingInvalidLogoutSuccessUrlThenThrowsException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.configLocations(xml("InvalidLogoutSuccessUrl")).autowire()); - } - - @Test - public void logoutWhenSpecifyingCookiesToDeleteThenSetCookieAdded() throws Exception { - this.spring.configLocations(xml("DeleteCookies")).autowire(); - MvcResult result = this.mvc.perform(post("/logout").with(csrf())).andReturn(); - List values = result.getResponse().getHeaders("Set-Cookie"); - assertThat(values).hasSize(2); - assertThat(values).extracting((value) -> value.split("=")[0]).contains("JSESSIONID", "mycookie"); - } - - @Test - public void logoutWhenSpecifyingSuccessHandlerRefThenResponseHandledAccordingly() throws Exception { - this.spring.configLocations(xml("LogoutSuccessHandlerRef")).autowire(); - // @formatter:off - this.mvc.perform(post("/logout").with(csrf())) - .andExpect(redirectedUrl("/logoutSuccessEndpoint")); - // @formatter:on - } - - @Test - public void getWhenUnauthenticatedThenUsesConfiguredRequestCache() throws Exception { - this.spring.configLocations(xml("RequestCache")).autowire(); - RequestCache requestCache = this.spring.getContext().getBean(RequestCache.class); - this.mvc.perform(get("/")); - verify(requestCache).saveRequest(any(HttpServletRequest.class), any(HttpServletResponse.class)); - } - - @Test - public void getWhenUnauthenticatedThenUsesConfiguredAuthenticationEntryPoint() throws Exception { - this.spring.configLocations(xml("EntryPoint")).autowire(); - AuthenticationEntryPoint entryPoint = this.spring.getContext().getBean(AuthenticationEntryPoint.class); - this.mvc.perform(get("/")); - verify(entryPoint).commence(any(HttpServletRequest.class), any(HttpServletResponse.class), - any(AuthenticationException.class)); - } - - /** - * See SEC-750. If the http security post processor causes beans to be instantiated - * too eagerly, they way miss additional processing. In this method we have a - * UserDetailsService which is referenced from the namespace and also has a post - * processor registered which will modify it. - */ - @Test - public void configureWhenUsingCustomUserDetailsServiceThenBeanPostProcessorsAreStillApplied() { - this.spring.configLocations(xml("Sec750")).autowire(); - BeanNameCollectingPostProcessor postProcessor = this.spring.getContext() - .getBean(BeanNameCollectingPostProcessor.class); - assertThat(postProcessor.getBeforeInitPostProcessedBeans()).contains("authenticationProvider", "userService"); - assertThat(postProcessor.getAfterInitPostProcessedBeans()).contains("authenticationProvider", "userService"); - } - - /* SEC-934 */ - @Test - public void getWhenUsingTwoIdenticalInterceptUrlsThenTheSecondTakesPrecedence() throws Exception { - this.spring.configLocations(xml("Sec934")).autowire(); - // @formatter:off - this.mvc.perform(get("/protected").with(userCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(get("/protected").with(adminCredentials())) - .andExpect(status().isForbidden()); - // @formatter:on - } - - @Test - public void getWhenAuthenticatingThenConsultsCustomSecurityContextRepository() throws Exception { - this.spring.configLocations(xml("SecurityContextRepository")).autowire(); - SecurityContextRepository repository = this.spring.getContext().getBean(SecurityContextRepository.class); - SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "password")); - given(repository.loadDeferredContext(any(HttpServletRequest.class))) - .willReturn(new TestDeferredSecurityContext(context, false)); - // @formatter:off - MvcResult result = this.mvc.perform(get("/protected").with(userCredentials())) - .andExpect(status().isOk()) - .andReturn(); - // @formatter:on - assertThat(result.getRequest().getSession(false)).isNotNull(); - } - - @Test - public void getWhenExplicitSaveAndRepositoryAndAuthenticatingThenConsultsCustomSecurityContextRepository() - throws Exception { - this.spring.configLocations(xml("ExplicitSaveAndExplicitRepository")).autowire(); - SecurityContextRepository repository = this.spring.getContext().getBean(SecurityContextRepository.class); - SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "password")); - given(repository.loadDeferredContext(any(HttpServletRequest.class))) - .willReturn(new TestDeferredSecurityContext(context, false)); - // @formatter:off - MvcResult result = this.mvc.perform(formLogin()) - .andExpect(status().is3xxRedirection()) - .andReturn(); - // @formatter:on - verify(repository, atLeastOnce()).saveContext(any(SecurityContext.class), any(HttpServletRequest.class), - any(HttpServletResponse.class)); - } - - @Test - public void getWhenExplicitSaveAndExplicitSaveAndAuthenticatingThenConsultsCustomSecurityContextRepository() - throws Exception { - this.spring.configLocations(xml("ExplicitSave")).autowire(); - SecurityContextRepository repository = this.spring.getContext().getBean(SecurityContextRepository.class); - // @formatter:off - MvcResult result = this.mvc.perform(formLogin()) - .andExpect(status().is3xxRedirection()) - .andReturn(); - // @formatter:on - assertThat(repository.loadContext(new HttpRequestResponseHolder(result.getRequest(), result.getResponse())) - .getAuthentication()).isNotNull(); - } - - @Test - public void getWhenUsingInterceptUrlExpressionsThenAuthorizesAccordingly() throws Exception { - this.spring.configLocations(xml("InterceptUrlExpressions")).autowire(); - // @formatter:off - this.mvc.perform(get("/protected").with(adminCredentials())) - .andExpect(status().isOk()); - this.mvc.perform(get("/protected").with(userCredentials())) - .andExpect(status().isForbidden()); - this.mvc.perform(get("/unprotected").with(userCredentials())) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void getWhenUsingCustomExpressionHandlerThenAuthorizesAccordingly() throws Exception { - this.spring.configLocations(xml("ExpressionHandler")).autowire(); - PermissionEvaluator permissionEvaluator = this.spring.getContext().getBean(PermissionEvaluator.class); - given(permissionEvaluator.hasPermission(any(Authentication.class), any(Object.class), any(Object.class))) - .willReturn(false); - // @formatter:off - this.mvc.perform(get("/").with(userCredentials())) - .andExpect(status().isForbidden()); - // @formatter:on - verify(permissionEvaluator).hasPermission(any(Authentication.class), any(Object.class), any(Object.class)); - } - - @Test - public void configureWhenProtectingLoginPageThenWarningLogged() { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - redirectLogsTo(baos, DefaultFilterChainValidator.class); - this.spring.configLocations(xml("ProtectedLoginPage")).autowire(); - assertThat(baos.toString()).contains("[WARN]"); - } - - @Test - public void configureWhenProtectingLoginPageAuthorizationManagerThenWarningLogged() { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - redirectLogsTo(baos, DefaultFilterChainValidator.class); - this.spring.configLocations(xml("ProtectedLoginPageAuthorizationManager")).autowire(); - assertThat(baos.toString()).contains("[WARN]"); - } - - @Test - public void configureWhenUsingDisableUrlRewritingThenRedirectIsNotEncodedByResponse() - throws IOException, ServletException { - this.spring.configLocations(xml("DisableUrlRewriting")).autowire(); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChainProxy proxy = this.spring.getContext().getBean(FilterChainProxy.class); - proxy.doFilter(request, new EncodeUrlDenyingHttpServletResponseWrapper(response), (req, resp) -> { - }); - assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); - assertThat(response.getRedirectedUrl()).isEqualTo("/login"); - } - - @Test - public void configureWhenUsingDisableUrlRewritingAndCustomRepositoryThenRedirectIsNotEncodedByResponse() - throws IOException, ServletException { - this.spring.configLocations(xml("DisableUrlRewriting-NullSecurityContextRepository")).autowire(); - MockHttpServletRequest request = new MockHttpServletRequest("GET", "/"); - MockHttpServletResponse responseToSpy = spy(new MockHttpServletResponse()); - FilterChainProxy proxy = this.spring.getContext().getBean(FilterChainProxy.class); - proxy.doFilter(request, responseToSpy, (req, resp) -> { - HttpServletResponse httpResponse = (HttpServletResponse) resp; - httpResponse.encodeURL("/"); - httpResponse.encodeRedirectURL("/"); - httpResponse.getWriter().write("encodeRedirect"); - }); - verify(responseToSpy, never()).encodeRedirectURL(any()); - verify(responseToSpy, never()).encodeURL(any()); - assertThat(responseToSpy.getContentAsString()).isEqualTo("encodeRedirect"); - } - - @Test - public void configureWhenUserDetailsServiceInParentContextThenLocatesSuccessfully() { - assertThatExceptionOfType(BeansException.class).isThrownBy( - () -> this.spring.configLocations(MiscHttpConfigTests.xml("MissingUserDetailsService")).autowire()); - try (XmlWebApplicationContext parent = new XmlWebApplicationContext()) { - parent.setConfigLocations(MiscHttpConfigTests.xml("AutoConfig")); - parent.refresh(); - try (XmlWebApplicationContext child = new XmlWebApplicationContext()) { - child.setParent(parent); - child.setConfigLocation(MiscHttpConfigTests.xml("MissingUserDetailsService")); - child.refresh(); - } - } - } - - @Test - public void loginWhenConfiguredWithNoInternalAuthenticationProvidersThenSuccessfullyAuthenticates() - throws Exception { - this.spring.configLocations(xml("NoInternalAuthenticationProviders")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .param("username", "user") - .param("password", "password"); - this.mvc.perform(loginRequest) - .andExpect(redirectedUrl("/")); - // @formatter:on - } - - @Test - public void loginWhenUsingDefaultsThenErasesCredentialsAfterAuthentication() throws Exception { - this.spring.configLocations(xml("HttpBasic")).autowire(); - // @formatter:off - this.mvc.perform(get("/password").with(userCredentials())) - .andExpect(content().string("")); - // @formatter:on - } - - @Test - public void loginWhenAuthenticationManagerConfiguredToEraseCredentialsThenErasesCredentialsAfterAuthentication() - throws Exception { - this.spring.configLocations(xml("AuthenticationManagerEraseCredentials")).autowire(); - // @formatter:off - this.mvc.perform(get("/password").with(userCredentials())) - .andExpect(content().string("")); - // @formatter:on - } - - /** - * SEC-2020 - */ - @Test - public void loginWhenAuthenticationManagerRefConfiguredToKeepCredentialsThenKeepsCredentialsAfterAuthentication() - throws Exception { - this.spring.configLocations(xml("AuthenticationManagerRefKeepCredentials")).autowire(); - // @formatter:off - this.mvc.perform(get("/password").with(userCredentials())) - .andExpect(content().string("password")); - // @formatter:on - } - - @Test - public void loginWhenAuthenticationManagerRefIsNotAProviderManagerThenKeepsCredentialsAccordingly() - throws Exception { - this.spring.configLocations(xml("AuthenticationManagerRefNotProviderManager")).autowire(); - // @formatter:off - this.mvc.perform(get("/password").with(userCredentials())) - .andExpect(content().string("password")); - // @formatter:on - } - - @Test - public void loginWhenJeeFilterThenExtractsRoles() throws Exception { - this.spring.configLocations(xml("JeeFilter")).autowire(); - Principal user = mock(Principal.class); - given(user.getName()).willReturn("joe"); - // @formatter:off - MockHttpServletRequestBuilder rolesRequest = get("/roles") - .principal(user) - .with((request) -> { - request.addUserRole("admin"); - request.addUserRole("user"); - request.addUserRole("unmapped"); - return request; - }); - this.mvc.perform(rolesRequest) - .andExpect(content().string("ROLE_admin,ROLE_user")); - // @formatter:on - } - - @Test - public void loginWhenJeeFilterCustomSecurityContextHolderStrategyThenUses() throws Exception { - this.spring.configLocations(xml("JeeFilterWithSecurityContextHolderStrategy")).autowire(); - Principal user = mock(Principal.class); - given(user.getName()).willReturn("joe"); - // @formatter:off - MockHttpServletRequestBuilder rolesRequest = get("/roles") - .principal(user) - .with((request) -> { - request.addUserRole("admin"); - request.addUserRole("user"); - request.addUserRole("unmapped"); - return request; - }); - this.mvc.perform(rolesRequest) - .andExpect(content().string("ROLE_admin,ROLE_user")); - // @formatter:on - verify(this.spring.getContext().getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); - } - - @Test - public void loginWhenUsingCustomAuthenticationDetailsSourceRefThenAuthenticationSourcesDetailsAccordingly() - throws Exception { - this.spring.configLocations(xml("CustomAuthenticationDetailsSourceRef")).autowire(); - Object details = mock(Object.class); - AuthenticationDetailsSource source = this.spring.getContext().getBean(AuthenticationDetailsSource.class); - given(source.buildDetails(any(Object.class))).willReturn(details); - RequestPostProcessor x509 = x509( - "classpath:org/springframework/security/config/http/MiscHttpConfigTests-certificate.pem"); - // @formatter:off - this.mvc.perform(get("/details").with(userCredentials())) - .andExpect(content().string(details.getClass().getName())); - this.mvc.perform(get("/details").with(x509)) - .andExpect(content().string(details.getClass().getName())); - MockHttpServletRequestBuilder loginRequest = post("/login") - .param("username", "user") - .param("password", "password") - .with(csrf()); - MockHttpSession session = (MockHttpSession) this.mvc.perform(loginRequest) - .andReturn() - .getRequest() - .getSession(false); - this.mvc.perform(get("/details").session(session)) - .andExpect(content().string(details.getClass().getName())); - // @formatter:on - } - - @Test - public void loginWhenUsingJaasApiProvisionThenJaasSubjectContainsUsername() throws Exception { - this.spring.configLocations(xml("Jaas")).autowire(); - AuthorityGranter granter = this.spring.getContext().getBean(AuthorityGranter.class); - given(granter.grant(any(Principal.class))).willReturn(new HashSet<>(Arrays.asList("USER"))); - // @formatter:off - this.mvc.perform(get("/username").with(userCredentials())) - .andExpect(content().string("user")); - // @formatter:on - } - - @Test - public void getWhenUsingCustomHttpFirewallThenFirewallIsInvoked() throws Exception { - this.spring.configLocations(xml("HttpFirewall")).autowire(); - FirewalledRequest request = new FirewalledRequest(new MockHttpServletRequest()) { - @Override - public void reset() { - } - }; - HttpServletResponse response = new MockHttpServletResponse(); - HttpFirewall firewall = this.spring.getContext().getBean(HttpFirewall.class); - given(firewall.getFirewalledRequest(any(HttpServletRequest.class))).willReturn(request); - given(firewall.getFirewalledResponse(any(HttpServletResponse.class))).willReturn(response); - this.mvc.perform(get("/unprotected")); - verify(firewall).getFirewalledRequest(any(HttpServletRequest.class)); - verify(firewall).getFirewalledResponse(any(HttpServletResponse.class)); - } - - @Test - public void getWhenUsingCustomRequestRejectedHandlerThenRequestRejectedHandlerIsInvoked() throws Exception { - this.spring.configLocations(xml("RequestRejectedHandler")).autowire(); - HttpServletResponse response = new MockHttpServletResponse(); - RequestRejectedException rejected = new RequestRejectedException("failed"); - HttpFirewall firewall = this.spring.getContext().getBean(HttpFirewall.class); - RequestRejectedHandler requestRejectedHandler = this.spring.getContext().getBean(RequestRejectedHandler.class); - given(firewall.getFirewalledRequest(any(HttpServletRequest.class))).willThrow(rejected); - this.mvc.perform(get("/unprotected")); - verify(requestRejectedHandler).handle(any(), any(), any()); - } - - @Test - public void getWhenUsingCustomAccessDecisionManagerThenAuthorizesAccordingly() throws Exception { - this.spring.configLocations(xml("CustomAccessDecisionManager")).autowire(); - // @formatter:off - this.mvc.perform(get("/unprotected").with(userCredentials())) - .andExpect(status().isForbidden()); - // @formatter:on - } - - @Test - public void asyncDispatchWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { - this.spring.configLocations(xml("WithSecurityContextHolderStrategy")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder requestWithBob = get("/name").with(user("Bob")); - MvcResult mvcResult = this.mvc.perform(requestWithBob) - .andExpect(request().asyncStarted()) - .andReturn(); - this.mvc.perform(asyncDispatch(mvcResult)) - .andExpect(status().isOk()) - .andExpect(content().string("Bob")); - // @formatter:on - verify(this.spring.getContext().getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); - } - - /** - * SEC-1893 - */ - @Test - public void authenticateWhenUsingPortMapperThenRedirectsAppropriately() throws Exception { - this.spring.configLocations(xml("PortsMappedRequiresHttps")).autowire(); - // @formatter:off - MockHttpSession session = (MockHttpSession) this.mvc.perform(get("https://localhost:9080/protected")) - .andExpect(redirectedUrl("/login")) - .andReturn() - .getRequest() - .getSession(false); - MockHttpServletRequestBuilder loginRequest = post("/login") - .param("username", "user") - .param("password", "password") - .session(session) - .with(csrf()); - session = (MockHttpSession) this.mvc.perform(loginRequest) - .andExpect(RequestCacheResultMatcher.redirectToCachedRequest()) - .andReturn() - .getRequest() - .getSession(false); - this.mvc.perform(get("http://localhost:9080/protected").session(session)) - .andExpect(redirectedUrl("https://localhost:9443/protected")); - // @formatter:on - } - - private void redirectLogsTo(OutputStream os, Class clazz) { - Logger logger = (Logger) LoggerFactory.getLogger(clazz); - Appender appender = mock(Appender.class); - given(appender.isStarted()).willReturn(true); - willAnswer(writeTo(os)).given(appender).doAppend(any(ILoggingEvent.class)); - logger.addAppender(appender); - } - - private Answer writeTo(OutputStream os) { - return (invocation) -> { - os.write(invocation.getArgument(0).toString().getBytes()); - return null; - }; - } - - private void assertThatFiltersMatchExpectedAutoConfigList() { - assertThatFiltersMatchExpectedAutoConfigList("/"); - } - - private void assertThatFiltersMatchExpectedAutoConfigList(String url) { - Iterator filters = getFilters(url).iterator(); - assertThat(filters.next()).isInstanceOf(DisableEncodeUrlFilter.class); - assertThat(filters.next()).isInstanceOf(SecurityContextHolderFilter.class); - assertThat(filters.next()).isInstanceOf(WebAsyncManagerIntegrationFilter.class); - assertThat(filters.next()).isInstanceOf(HeaderWriterFilter.class); - assertThat(filters.next()).isInstanceOf(CsrfFilter.class); - assertThat(filters.next()).isInstanceOf(LogoutFilter.class); - assertThat(filters.next()).isInstanceOf(UsernamePasswordAuthenticationFilter.class); - assertThat(filters.next()).isInstanceOf(DefaultResourcesFilter.class); - assertThat(filters.next()).isInstanceOf(DefaultLoginPageGeneratingFilter.class); - assertThat(filters.next()).isInstanceOf(DefaultLogoutPageGeneratingFilter.class); - assertThat(filters.next()).isInstanceOf(BasicAuthenticationFilter.class); - assertThat(filters.next()).isInstanceOf(RequestCacheAwareFilter.class); - assertThat(filters.next()).isInstanceOf(SecurityContextHolderAwareRequestFilter.class); - assertThat(filters.next()).isInstanceOf(AnonymousAuthenticationFilter.class); - assertThat(filters.next()).isInstanceOf(ExceptionTranslationFilter.class); - assertThat(filters.next()).isInstanceOf(AuthorizationFilter.class); - } - - private T getFilter(Class filterClass) { - return (T) getFilters("/").stream().filter(filterClass::isInstance).findFirst().orElse(null); - } - - private List getFilters(String url) { - FilterChainProxy proxy = this.spring.getContext().getBean(FilterChainProxy.class); - return proxy.getFilters(url); - } - - @NonNull - private static RequestPostProcessor userCredentials() { - return httpBasic("user", "password"); - } - - @NonNull - private static RequestPostProcessor adminCredentials() { - return httpBasic("admin", "password"); - } - - @NonNull - private static RequestPostProcessor postCredentials() { - return httpBasic("poster", "password"); - } - - private static String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - @RestController - static class BasicController { - - @RequestMapping("/unprotected") - String unprotected() { - return "ok"; - } - - @RequestMapping("/protected") - String protectedMethod(@AuthenticationPrincipal String name) { - return name; - } - - } - - @RestController - static class CustomKeyController { - - @GetMapping("/customKey") - String customKey() { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null && authentication instanceof AnonymousAuthenticationToken) { - return String.valueOf(((AnonymousAuthenticationToken) authentication).getKeyHash()); - } - return null; - } - - } - - @RestController - static class AuthenticationController { - - @GetMapping("/password") - String password(Authentication authentication) { - return (String) authentication.getCredentials(); - } - - @GetMapping("/roles") - String roles(Authentication authentication) { - return authentication.getAuthorities() - .stream() - .map(GrantedAuthority::getAuthority) - .collect(Collectors.joining(",")); - } - - @GetMapping("/details") - String details(Authentication authentication) { - return authentication.getDetails().getClass().getName(); - } - - @GetMapping("/name") - Callable name(Authentication authentication) { - return () -> authentication.getName(); - } - - } - - @RestController - static class JaasController { - - @GetMapping("/username") - String username() { - Subject subject = Subject.getSubject(AccessController.getContext()); - return subject.getPrincipals().iterator().next().getName(); - } - - } - - public static class JaasLoginModule implements LoginModule { - - private Subject subject; - - @Override - public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, - Map options) { - this.subject = subject; - } - - @Override - public boolean login() { - return this.subject.getPrincipals().add(() -> "user"); - } - - @Override - public boolean commit() { - return true; - } - - @Override - public boolean abort() { - return true; - } - - @Override - public boolean logout() { - return true; - } - - } - - static class MockAccessDecisionManager implements AccessDecisionManager { - - @Override - public void decide(Authentication authentication, Object object, Collection configAttributes) - throws AccessDeniedException, InsufficientAuthenticationException { - throw new AccessDeniedException("teapot"); - } - - @Override - public boolean supports(ConfigAttribute attribute) { - return true; - } - - @Override - public boolean supports(Class clazz) { - return true; - } - - } - - static class MockAuthenticationManager implements AuthenticationManager { - - @Override - public Authentication authenticate(Authentication authentication) { - return new TestingAuthenticationToken(authentication.getPrincipal(), authentication.getCredentials(), - AuthorityUtils.createAuthorityList("ROLE_USER")); - } - - } - - static class EncodeUrlDenyingHttpServletResponseWrapper extends HttpServletResponseWrapper { - - EncodeUrlDenyingHttpServletResponseWrapper(HttpServletResponse response) { - super(response); - } - - @Override - public String encodeURL(String url) { - throw new RuntimeException("Unexpected invocation of encodeURL"); - } - - @Override - public String encodeRedirectURL(String url) { - throw new RuntimeException("Unexpected invocation of encodeURL"); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/MultiHttpBlockConfigTests.java b/config/src/test/java/org/springframework/security/config/http/MultiHttpBlockConfigTests.java deleted file mode 100644 index 896f1aa1a80..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/MultiHttpBlockConfigTests.java +++ /dev/null @@ -1,120 +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.http; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.stereotype.Controller; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.web.bind.annotation.GetMapping; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests scenarios with multiple <http> elements. - * - * @author Luke Taylor - */ -@ExtendWith(SpringTestContextExtension.class) -public class MultiHttpBlockConfigTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/MultiHttpBlockConfigTests"; - - @Autowired - MockMvc mvc; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - public void requestWhenUsingMutuallyExclusiveHttpElementsThenIsRoutedAccordingly() throws Exception { - this.spring.configLocations(this.xml("DistinctHttpElements")).autowire(); - // @formatter:off - this.mvc.perform(get("/first").with(httpBasic("user", "password"))) - .andExpect(status().isOk()); - MockHttpServletRequestBuilder formLoginRequest = post("/second/login") - .param("username", "user") - .param("password", "password") - .with(csrf()); - this.mvc.perform(formLoginRequest) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/")); - // @formatter:on - } - - @Test - public void configureWhenUsingDuplicateHttpElementsThenThrowsWiringException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("IdenticalHttpElements")).autowire()) - .withCauseInstanceOf(IllegalArgumentException.class); - } - - @Test - public void configureWhenUsingIndenticallyPatternedHttpElementsThenThrowsWiringException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("IdenticallyPatternedHttpElements")).autowire()) - .withCauseInstanceOf(IllegalArgumentException.class); - } - - /** - * SEC-1937 - */ - @Test - public void requestWhenTargettingAuthenticationManagersToCorrespondingHttpElementsThenAuthenticationProceeds() - throws Exception { - this.spring.configLocations(this.xml("Sec1937")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder basicLoginRequest = get("/first") - .with(httpBasic("first", "password")) - .with(csrf()); - this.mvc.perform(basicLoginRequest) - .andExpect(status().isOk()); - MockHttpServletRequestBuilder formLoginRequest = post("/second/login") - .param("username", "second") - .param("password", "password") - .with(csrf()); - this.mvc.perform(formLoginRequest) - .andExpect(redirectedUrl("/")); - // @formatter:on - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - @Controller - static class BasicController { - - @GetMapping("/first") - String first() { - return "ok"; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/NamespaceHttpBasicTests.java b/config/src/test/java/org/springframework/security/config/http/NamespaceHttpBasicTests.java deleted file mode 100644 index 2064631ae8b..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/NamespaceHttpBasicTests.java +++ /dev/null @@ -1,144 +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.http; - -import java.lang.reflect.Method; -import java.util.Base64; - -import jakarta.servlet.Filter; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mock; - -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.config.util.InMemoryXmlApplicationContext; -import org.springframework.security.core.context.SecurityContextHolderStrategy; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.verify; - -/** - * @author Rob Winch - */ -public class NamespaceHttpBasicTests { - - @Mock - Method method; - - MockHttpServletRequest request; - - MockHttpServletResponse response; - - MockFilterChain chain; - - ConfigurableApplicationContext context; - - Filter springSecurityFilterChain; - - @BeforeEach - public void setup() { - this.request = new MockHttpServletRequest("GET", ""); - this.request.setMethod("GET"); - this.response = new MockHttpServletResponse(); - this.chain = new MockFilterChain(); - } - - @AfterEach - public void teardown() { - if (this.context != null) { - this.context.close(); - } - } - - // gh-3296 - @Test - public void httpBasicWithPasswordEncoder() throws Exception { - // @formatter:off - loadContext("\n" - + " \n" - + " \n" - + "\n" - + "\n" - + "\n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + "\n" - + ""); - // @formatter:on - this.request.addHeader("Authorization", - "Basic " + Base64.getEncoder().encodeToString("user:test".getBytes("UTF-8"))); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - } - - @Test - public void httpBasicCustomSecurityContextHolderStrategy() throws Exception { - // @formatter:off - loadContext("\n" - + "\n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + "\n" - + "\n" + - " \n" + - " \n" + - " \n" + - ""); - // @formatter:on - this.request.addHeader("Authorization", - "Basic " + Base64.getEncoder().encodeToString("user:test".getBytes("UTF-8"))); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - verify(this.context.getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); - } - - // gh-4220 - @Test - public void httpBasicUnauthorizedOnDefault() throws Exception { - // @formatter:off - loadContext("\n" - + " \n" - + " \n" - + "\n" - + "\n" - + ""); - // @formatter:on - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED); - assertThat(this.response.getHeader("WWW-Authenticate")).isEqualTo("Basic realm=\"Realm\""); - } - - private void loadContext(String context) { - this.context = new InMemoryXmlApplicationContext(context); - this.springSecurityFilterChain = this.context.getBean("springSecurityFilterChain", Filter.class); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests.java b/config/src/test/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests.java deleted file mode 100644 index 25790c7f55c..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests.java +++ /dev/null @@ -1,410 +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.http; - -import java.time.Duration; -import java.time.Instant; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException; -import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.OAuth2AuthorizationContext; -import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.TokenExchangeOAuth2AuthorizedClientProvider; -import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest; -import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest; -import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest; -import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest; -import org.springframework.security.oauth2.client.endpoint.TokenExchangeGrantRequest; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthorizationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; -import org.springframework.security.oauth2.jwt.JoseHeaderNames; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimNames; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link OAuth2AuthorizedClientManagerRegistrar}. - * - * @author Steve Riesenberg - */ -public class OAuth2AuthorizedClientManagerRegistrarTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests"; - - private static OAuth2AccessTokenResponseClient MOCK_RESPONSE_CLIENT; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private OAuth2AuthorizedClientManager authorizedClientManager; - - @Autowired - private ClientRegistrationRepository clientRegistrationRepository; - - @Autowired - private OAuth2AuthorizedClientRepository authorizedClientRepository; - - @Autowired(required = false) - private AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeAuthorizedClientProvider; - - private MockHttpServletRequest request; - - private MockHttpServletResponse response; - - @BeforeEach - @SuppressWarnings("unchecked") - public void setUp() { - MOCK_RESPONSE_CLIENT = mock(OAuth2AccessTokenResponseClient.class); - this.request = new MockHttpServletRequest(); - this.response = new MockHttpServletResponse(); - } - - @Test - public void loadContextWhenOAuth2ClientEnabledThenConfigured() { - this.spring.configLocations(xml("minimal")).autowire(); - assertThat(this.authorizedClientManager).isNotNull(); - } - - @Test - public void authorizeWhenAuthorizationCodeAuthorizedClientProviderBeanThenUsed() { - this.spring.configLocations(xml("providers")).autowire(); - - TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId("google") - .principal(authentication) - .attribute(HttpServletRequest.class.getName(), this.request) - .attribute(HttpServletResponse.class.getName(), this.response) - .build(); - assertThatExceptionOfType(ClientAuthorizationRequiredException.class) - .isThrownBy(() -> this.authorizedClientManager.authorize(authorizeRequest)) - .extracting(OAuth2AuthorizationException::getError) - .extracting(OAuth2Error::getErrorCode) - .isEqualTo("client_authorization_required"); - // @formatter:on - - verify(this.authorizationCodeAuthorizedClientProvider).authorize(any(OAuth2AuthorizationContext.class)); - } - - @Test - public void authorizeWhenRefreshTokenAccessTokenResponseClientBeanThenUsed() { - this.spring.configLocations(xml("clients")).autowire(); - testRefreshTokenGrant(); - } - - @Test - public void authorizeWhenRefreshTokenAuthorizedClientProviderBeanThenUsed() { - this.spring.configLocations(xml("providers")).autowire(); - testRefreshTokenGrant(); - } - - private void testRefreshTokenGrant() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class))) - .willReturn(accessTokenResponse); - - TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google"); - OAuth2AuthorizedClient existingAuthorizedClient = new OAuth2AuthorizedClient(clientRegistration, - authentication.getName(), getExpiredAccessToken(), TestOAuth2RefreshTokens.refreshToken()); - this.authorizedClientRepository.saveAuthorizedClient(existingAuthorizedClient, authentication, this.request, - this.response); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withAuthorizedClient(existingAuthorizedClient) - .principal(authentication) - .attribute(HttpServletRequest.class.getName(), this.request) - .attribute(HttpServletResponse.class.getName(), this.response) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - assertThat(authorizedClient).isNotNull(); - - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(OAuth2RefreshTokenGrantRequest.class); - verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); - - OAuth2RefreshTokenGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getClientRegistration().getRegistrationId()) - .isEqualTo(clientRegistration.getRegistrationId()); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN); - assertThat(grantRequest.getAccessToken()).isEqualTo(existingAuthorizedClient.getAccessToken()); - assertThat(grantRequest.getRefreshToken()).isEqualTo(existingAuthorizedClient.getRefreshToken()); - } - - @Test - public void authorizeWhenClientCredentialsAccessTokenResponseClientBeanThenUsed() { - this.spring.configLocations(xml("clients")).autowire(); - testClientCredentialsGrant(); - } - - @Test - public void authorizeWhenClientCredentialsAuthorizedClientProviderBeanThenUsed() { - this.spring.configLocations(xml("providers")).autowire(); - testClientCredentialsGrant(); - } - - private void testClientCredentialsGrant() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2ClientCredentialsGrantRequest.class))) - .willReturn(accessTokenResponse); - - TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github"); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(clientRegistration.getRegistrationId()) - .principal(authentication) - .attribute(HttpServletRequest.class.getName(), this.request) - .attribute(HttpServletResponse.class.getName(), this.response) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - assertThat(authorizedClient).isNotNull(); - - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(OAuth2ClientCredentialsGrantRequest.class); - verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); - - OAuth2ClientCredentialsGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getClientRegistration().getRegistrationId()) - .isEqualTo(clientRegistration.getRegistrationId()); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS); - } - - @Test - public void authorizeWhenJwtBearerAccessTokenResponseClientBeanThenUsed() { - this.spring.configLocations(xml("clients")).autowire(); - testJwtBearerGrant(); - } - - @Test - public void authorizeWhenJwtBearerAuthorizedClientProviderBeanThenUsed() { - this.spring.configLocations(xml("providers")).autowire(); - testJwtBearerGrant(); - } - - private void testJwtBearerGrant() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(JwtBearerGrantRequest.class))).willReturn(accessTokenResponse); - - JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt()); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("okta"); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(clientRegistration.getRegistrationId()) - .principal(authentication) - .attribute(HttpServletRequest.class.getName(), this.request) - .attribute(HttpServletResponse.class.getName(), this.response) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - assertThat(authorizedClient).isNotNull(); - - ArgumentCaptor grantRequestCaptor = ArgumentCaptor.forClass(JwtBearerGrantRequest.class); - verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); - - JwtBearerGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getClientRegistration().getRegistrationId()) - .isEqualTo(clientRegistration.getRegistrationId()); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.JWT_BEARER); - assertThat(grantRequest.getJwt().getSubject()).isEqualTo("user"); - } - - @Test - public void authorizeWhenTokenExchangeAccessTokenResponseClientBeanThenUsed() { - this.spring.configLocations(xml("clients")).autowire(); - testTokenExchangeGrant(); - } - - @Test - public void authorizeWhenTokenExchangeAuthorizedClientProviderBeanThenUsed() { - this.spring.configLocations(xml("providers")).autowire(); - testTokenExchangeGrant(); - } - - private void testTokenExchangeGrant() { - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(TokenExchangeGrantRequest.class))) - .willReturn(accessTokenResponse); - - JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt()); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("auth0"); - // @formatter:off - OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest - .withClientRegistrationId(clientRegistration.getRegistrationId()) - .principal(authentication) - .attribute(HttpServletRequest.class.getName(), this.request) - .attribute(HttpServletResponse.class.getName(), this.response) - .build(); - // @formatter:on - OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest); - assertThat(authorizedClient).isNotNull(); - - ArgumentCaptor grantRequestCaptor = ArgumentCaptor - .forClass(TokenExchangeGrantRequest.class); - verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture()); - - TokenExchangeGrantRequest grantRequest = grantRequestCaptor.getValue(); - assertThat(grantRequest.getClientRegistration().getRegistrationId()) - .isEqualTo(clientRegistration.getRegistrationId()); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.TOKEN_EXCHANGE); - assertThat(grantRequest.getSubjectToken()).isEqualTo(authentication.getToken()); - } - - private static OAuth2AccessToken getExpiredAccessToken() { - Instant expiresAt = Instant.now().minusSeconds(60); - Instant issuedAt = expiresAt.minus(Duration.ofDays(1)); - return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "scopes", issuedAt, expiresAt, - new HashSet<>(Arrays.asList("read", "write"))); - } - - private static Jwt getJwt() { - Instant issuedAt = Instant.now(); - return new Jwt("token", issuedAt, issuedAt.plusSeconds(300), - Collections.singletonMap(JoseHeaderNames.ALG, "RS256"), - Collections.singletonMap(JwtClaimNames.SUB, "user")); - } - - private static String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - public static List getClientRegistrations() { - // @formatter:off - return Arrays.asList( - CommonOAuth2Provider.GOOGLE.getBuilder("google") - .clientId("google-client-id") - .clientSecret("google-client-secret") - .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .build(), - CommonOAuth2Provider.GITHUB.getBuilder("github") - .clientId("github-client-id") - .clientSecret("github-client-secret") - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .build(), - CommonOAuth2Provider.OKTA.getBuilder("okta") - .clientId("okta-client-id") - .clientSecret("okta-client-secret") - .authorizationGrantType(AuthorizationGrantType.JWT_BEARER) - .build(), - ClientRegistration.withRegistrationId("auth0") - .clientName("Auth0") - .clientId("auth0-client-id") - .clientSecret("auth0-client-secret") - .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) - .authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE) - .scope("user.read", "user.write") - .build()); - // @formatter:on - } - - public static AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCode() { - return spy(new AuthorizationCodeOAuth2AuthorizedClientProvider()); - } - - public static RefreshTokenOAuth2AuthorizedClientProvider refreshToken() { - RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(refreshTokenAccessTokenResponseClient()); - return authorizedClientProvider; - } - - public static OAuth2AccessTokenResponseClient refreshTokenAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - - public static ClientCredentialsOAuth2AuthorizedClientProvider clientCredentials() { - ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(clientCredentialsAccessTokenResponseClient()); - return authorizedClientProvider; - } - - public static OAuth2AccessTokenResponseClient clientCredentialsAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - - public static JwtBearerOAuth2AuthorizedClientProvider jwtBearer() { - JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient()); - return authorizedClientProvider; - } - - public static OAuth2AccessTokenResponseClient jwtBearerAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - - public static TokenExchangeOAuth2AuthorizedClientProvider tokenExchange() { - TokenExchangeOAuth2AuthorizedClientProvider authorizedClientProvider = new TokenExchangeOAuth2AuthorizedClientProvider(); - authorizedClientProvider.setAccessTokenResponseClient(tokenExchangeAccessTokenResponseClient()); - return authorizedClientProvider; - } - - public static OAuth2AccessTokenResponseClient tokenExchangeAccessTokenResponseClient() { - return new MockAccessTokenResponseClient<>(); - } - - private static class MockAccessTokenResponseClient - implements OAuth2AccessTokenResponseClient { - - @Override - public OAuth2AccessTokenResponse getTokenResponse(T authorizationGrantRequest) { - return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests.java deleted file mode 100644 index d0987c1a741..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests.java +++ /dev/null @@ -1,262 +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.http; - -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; -import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.security.web.RedirectStrategy; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.ui.Model; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link OAuth2ClientBeanDefinitionParser}. - * - * @author Joe Grandja - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class OAuth2ClientBeanDefinitionParserTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/OAuth2ClientBeanDefinitionParserTests"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private ClientRegistrationRepository clientRegistrationRepository; - - @Autowired(required = false) - private OAuth2AuthorizedClientRepository authorizedClientRepository; - - @Autowired(required = false) - private OAuth2AuthorizedClientService authorizedClientService; - - @Autowired(required = false) - private AuthorizationRequestRepository authorizationRequestRepository; - - @Autowired(required = false) - private OAuth2AuthorizationRequestResolver authorizationRequestResolver; - - @Autowired(required = false) - private RedirectStrategy authorizationRedirectStrategy; - - @Autowired(required = false) - private OAuth2AccessTokenResponseClient accessTokenResponseClient; - - @Autowired - private MockMvc mvc; - - @Test - public void requestWhenAuthorizeThenRedirect() throws Exception { - this.spring.configLocations(xml("Minimal")).autowire(); - // @formatter:off - MvcResult result = this.mvc.perform(get("/oauth2/authorization/google")) - .andExpect(status().is3xxRedirection()) - .andReturn(); - // @formatter:on - assertThat(result.getResponse().getRedirectedUrl()).matches("https://accounts.google.com/o/oauth2/v2/auth\\?" - + "response_type=code&client_id=google-client-id&" - + "scope=scope1%20scope2&state=.{15,}&redirect_uri=http://localhost/callback/google&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256"); - } - - @Test - public void requestWhenCustomClientRegistrationRepositoryThenCalled() throws Exception { - this.spring.configLocations(xml("CustomClientRegistrationRepository")).autowire(); - // @formatter:off - ClientRegistration clientRegistration = CommonOAuth2Provider.GOOGLE.getBuilder("google") - .clientId("google-client-id") - .clientSecret("google-client-secret") - .redirectUri("http://localhost/callback/google") - .scope("scope1", "scope2") - .build(); - // @formatter:on - given(this.clientRegistrationRepository.findByRegistrationId(any())).willReturn(clientRegistration); - // @formatter:off - MvcResult result = this.mvc.perform(get("/oauth2/authorization/google")) - .andExpect(status().is3xxRedirection()) - .andReturn(); - // @formatter:on - assertThat(result.getResponse().getRedirectedUrl()).matches("https://accounts.google.com/o/oauth2/v2/auth\\?" - + "response_type=code&client_id=google-client-id&" - + "scope=scope1%20scope2&state=.{15,}&redirect_uri=http://localhost/callback/google&code_challenge=([a-zA-Z0-9\\-\\.\\_\\~]){43}&code_challenge_method=S256"); - verify(this.clientRegistrationRepository).findByRegistrationId(any()); - } - - @Test - public void requestWhenCustomAuthorizationRequestResolverThenCalled() throws Exception { - this.spring.configLocations(xml("CustomConfiguration")).autowire(); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google"); - OAuth2AuthorizationRequest authorizationRequest = createAuthorizationRequest(clientRegistration); - given(this.authorizationRequestResolver.resolve(any())).willReturn(authorizationRequest); - // @formatter:off - this.mvc.perform(get("/oauth2/authorization/google")) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("https://accounts.google.com/o/oauth2/v2/auth?" - + "response_type=code&client_id=google-client-id&" - + "scope=scope1%20scope2&state=state&redirect_uri=http://localhost/callback/google")); - // @formatter:on - verify(this.authorizationRequestResolver).resolve(any()); - } - - @Test - public void requestWhenCustomAuthorizationRedirectStrategyThenCalled() throws Exception { - this.spring.configLocations(xml("CustomAuthorizationRedirectStrategy")).autowire(); - // @formatter:off - this.mvc.perform(get("/oauth2/authorization/google")) - .andExpect(status().isOk()); - // @formatter:on - verify(this.authorizationRedirectStrategy).sendRedirect(any(), any(), anyString()); - } - - @Test - public void requestWhenAuthorizationResponseMatchThenProcess() throws Exception { - this.spring.configLocations(xml("CustomConfiguration")).autowire(); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google"); - OAuth2AuthorizationRequest authorizationRequest = createAuthorizationRequest(clientRegistration); - given(this.authorizationRequestRepository.loadAuthorizationRequest(any())).willReturn(authorizationRequest); - given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) - .willReturn(authorizationRequest); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("code", "code123"); - params.add("state", authorizationRequest.getState()); - // @formatter:off - this.mvc.perform(get(authorizationRequest.getRedirectUri()).params(params)) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl(authorizationRequest.getRedirectUri())); - // @formatter:on - ArgumentCaptor authorizedClientCaptor = ArgumentCaptor - .forClass(OAuth2AuthorizedClient.class); - verify(this.authorizedClientRepository).saveAuthorizedClient(authorizedClientCaptor.capture(), any(), any(), - any()); - OAuth2AuthorizedClient authorizedClient = authorizedClientCaptor.getValue(); - assertThat(authorizedClient.getClientRegistration()).isEqualTo(clientRegistration); - assertThat(authorizedClient.getAccessToken()).isEqualTo(accessTokenResponse.getAccessToken()); - } - - @WithMockUser - @Test - public void requestWhenCustomAuthorizedClientServiceThenCalled() throws Exception { - this.spring.configLocations(xml("CustomAuthorizedClientService")).autowire(); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google"); - OAuth2AuthorizationRequest authorizationRequest = createAuthorizationRequest(clientRegistration); - given(this.authorizationRequestRepository.loadAuthorizationRequest(any())).willReturn(authorizationRequest); - given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) - .willReturn(authorizationRequest); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("code", "code123"); - params.add("state", authorizationRequest.getState()); - // @formatter:off - this.mvc.perform(get(authorizationRequest.getRedirectUri()).params(params)) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl(authorizationRequest.getRedirectUri())); - // @formatter:on - verify(this.authorizedClientService).saveAuthorizedClient(any(), any()); - } - - @WithMockUser - @Test - public void requestWhenAuthorizedClientFoundThenMethodArgumentResolved() throws Exception { - this.spring.configLocations(xml("AuthorizedClientArgumentResolver")).autowire(); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google"); - OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, "user", - TestOAuth2AccessTokens.noScopes()); - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, null, request, response); - this.mvc.perform(get("/authorized-client").session((MockHttpSession) request.getSession())) - .andExpect(status().isOk()) - .andExpect(content().string("resolved")); - } - - private static OAuth2AuthorizationRequest createAuthorizationRequest(ClientRegistration clientRegistration) { - Map attributes = new HashMap<>(); - attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); - // @formatter:off - return OAuth2AuthorizationRequest.authorizationCode() - .authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri()) - .clientId(clientRegistration.getClientId()).redirectUri(clientRegistration.getRedirectUri()) - .scopes(clientRegistration.getScopes()) - .state("state") - .attributes(attributes) - .build(); - // @formatter:on - } - - private static String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - @RestController - static class AuthorizedClientController { - - @GetMapping("/authorized-client") - String authorizedClient(Model model, - @RegisteredOAuth2AuthorizedClient("google") OAuth2AuthorizedClient authorizedClient) { - return (authorizedClient != null) ? "resolved" : "not-resolved"; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java deleted file mode 100644 index 2847d306807..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests.java +++ /dev/null @@ -1,576 +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.http; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationListener; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.authentication.SecurityAssertions; -import org.springframework.security.authentication.event.AuthenticationSuccessEvent; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; -import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; -import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests; -import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser; -import org.springframework.security.oauth2.core.oidc.user.OidcUser; -import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.security.oauth2.core.user.TestOAuth2Users; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtDecoderFactory; -import org.springframework.security.oauth2.jwt.TestJwts; -import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.security.web.RedirectStrategy; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.savedrequest.RequestCache; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.ui.Model; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link OAuth2LoginBeanDefinitionParser}. - * - * @author Ruby Hartono - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class OAuth2LoginBeanDefinitionParserTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/OAuth2LoginBeanDefinitionParserTests"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private ClientRegistrationRepository clientRegistrationRepository; - - @Autowired(required = false) - private OAuth2AuthorizedClientRepository authorizedClientRepository; - - @Autowired(required = false) - private OAuth2AuthorizedClientService authorizedClientService; - - @Autowired(required = false) - private ApplicationListener authenticationSuccessListener; - - @Autowired(required = false) - private AuthorizationRequestRepository authorizationRequestRepository; - - @Autowired(required = false) - private OAuth2AuthorizationRequestResolver authorizationRequestResolver; - - @Autowired(required = false) - private RedirectStrategy authorizationRedirectStrategy; - - @Autowired(required = false) - private OAuth2AccessTokenResponseClient accessTokenResponseClient; - - @Autowired(required = false) - private OAuth2UserService oauth2UserService; - - @Autowired(required = false) - private OAuth2UserService oidcUserService; - - @Autowired(required = false) - private JwtDecoderFactory jwtDecoderFactory; - - @Autowired(required = false) - private GrantedAuthoritiesMapper userAuthoritiesMapper; - - @Autowired(required = false) - private AuthenticationFailureHandler authenticationFailureHandler; - - @Autowired(required = false) - private AuthenticationSuccessHandler authenticationSuccessHandler; - - @Autowired(required = false) - private RequestCache requestCache; - - @Autowired(required = false) - private SecurityContextHolderStrategy securityContextHolderStrategy; - - @Autowired - private MockMvc mvc; - - @Test - public void requestLoginWhenMultiClientRegistrationThenReturnLoginPageWithClients() throws Exception { - this.spring.configLocations(this.xml("MultiClientRegistration")).autowire(); - // @formatter:off - MvcResult result = this.mvc.perform(get("/login")) - .andExpect(status().is2xxSuccessful()) - .andReturn(); - // @formatter:on - assertThat(result.getResponse().getContentAsString()) - .contains("Google"); - assertThat(result.getResponse().getContentAsString()) - .contains("Github"); - } - - // gh-5347 - @Test - public void requestWhenSingleClientRegistrationThenAutoRedirect() throws Exception { - this.spring.configLocations(this.xml("SingleClientRegistration")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/oauth2/authorization/google-login")); - // @formatter:on - verify(this.requestCache).saveRequest(any(), any()); - } - - // gh-5347 - @Test - public void requestWhenSingleClientRegistrationAndRequestFaviconNotAuthenticatedThenRedirectDefaultLoginPage() - throws Exception { - this.spring.configLocations(this.xml("SingleClientRegistration")).autowire(); - // @formatter:off - this.mvc.perform(get("/favicon.ico").accept(new MediaType("image", "*"))) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/login")); - // @formatter:on - } - - // gh-6812 - @Test - public void requestWhenSingleClientRegistrationAndRequestXHRNotAuthenticatedThenDoesNotRedirectForAuthorization() - throws Exception { - this.spring.configLocations(this.xml("SingleClientRegistration")).autowire(); - // @formatter:off - this.mvc.perform(get("/").header("X-Requested-With", "XMLHttpRequest")) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/login")); - // @formatter:on - } - - @Test - public void requestWhenAuthorizationRequestNotFoundThenThrowAuthenticationException() throws Exception { - this.spring.configLocations(this.xml("SingleClientRegistration-WithCustomAuthenticationFailureHandler")) - .autowire(); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("code", "code123"); - params.add("state", "state123"); - this.mvc.perform(get("/login/oauth2/code/google").params(params)); - ArgumentCaptor exceptionCaptor = ArgumentCaptor - .forClass(AuthenticationException.class); - verify(this.authenticationFailureHandler).onAuthenticationFailure(any(), any(), exceptionCaptor.capture()); - AuthenticationException exception = exceptionCaptor.getValue(); - assertThat(exception).isInstanceOf(OAuth2AuthenticationException.class); - assertThat(((OAuth2AuthenticationException) exception).getError().getErrorCode()) - .isEqualTo("authorization_request_not_found"); - } - - @Test - public void requestWhenAuthorizationResponseValidThenAuthenticate() throws Exception { - this.spring.configLocations(this.xml("MultiClientRegistration-WithCustomConfiguration")).autowire(); - Map attributes = new HashMap<>(); - attributes.put(OAuth2ParameterNames.REGISTRATION_ID, "github-login"); - OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() - .attributes(attributes) - .build(); - given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) - .willReturn(authorizationRequest); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); - OAuth2User oauth2User = TestOAuth2Users.create(); - given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("code", "code123"); - params.add("state", authorizationRequest.getState()); - // @formatter:off - this.mvc.perform(get("/login/oauth2/code/github-login").params(params)) - .andExpect(status().is2xxSuccessful()); - // @formatter:on - ArgumentCaptor authenticationCaptor = ArgumentCaptor.forClass(Authentication.class); - verify(this.authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), authenticationCaptor.capture()); - Authentication authentication = authenticationCaptor.getValue(); - assertThat(authentication.getPrincipal()).isInstanceOf(OAuth2User.class); - } - - // gh-6009 - @Test - public void requestWhenAuthorizationResponseValidThenAuthenticationSuccessEventPublished() throws Exception { - this.spring.configLocations(this.xml("MultiClientRegistration-WithCustomConfiguration")).autowire(); - Map attributes = new HashMap<>(); - attributes.put(OAuth2ParameterNames.REGISTRATION_ID, "github-login"); - OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() - .attributes(attributes) - .build(); - given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) - .willReturn(authorizationRequest); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); - OAuth2User oauth2User = TestOAuth2Users.create(); - given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("code", "code123"); - params.add("state", authorizationRequest.getState()); - this.mvc.perform(get("/login/oauth2/code/github-login").params(params)); - verify(this.authenticationSuccessListener).onApplicationEvent(any(AuthenticationSuccessEvent.class)); - } - - @Test - public void requestWhenOidcAuthenticationResponseValidThenJwtDecoderFactoryCalled() throws Exception { - this.spring.configLocations(this.xml("SingleClientRegistration-WithJwtDecoderFactoryAndDefaultSuccessHandler")) - .autowire(); - Map attributes = new HashMap<>(); - attributes.put(OAuth2ParameterNames.REGISTRATION_ID, "google-login"); - OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.oidcRequest() - .attributes(attributes) - .build(); - given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) - .willReturn(authorizationRequest); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.oidcAccessTokenResponse() - .build(); - given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); - Jwt jwt = TestJwts.user(); - given(this.jwtDecoderFactory.createDecoder(any())).willReturn((token) -> jwt); - DefaultOidcUser oidcUser = TestOidcUsers.create(); - given(this.oidcUserService.loadUser(any())).willReturn(oidcUser); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("code", "code123"); - params.add("state", authorizationRequest.getState()); - // @formatter:off - this.mvc.perform(get("/login/oauth2/code/google-login").params(params)) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/")); - // @formatter:on - verify(this.jwtDecoderFactory).createDecoder(any()); - verify(this.requestCache).getRequest(any(), any()); - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - @Test - public void requestWhenCustomGrantedAuthoritiesMapperThenCalled() throws Exception { - this.spring.configLocations(this.xml("MultiClientRegistration-WithCustomGrantedAuthorities")).autowire(); - Map attributes = new HashMap<>(); - attributes.put(OAuth2ParameterNames.REGISTRATION_ID, "github-login"); - OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() - .attributes(attributes) - .build(); - given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) - .willReturn(authorizationRequest); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); - OAuth2User oauth2User = TestOAuth2Users.create(); - given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User); - given(this.userAuthoritiesMapper.mapAuthorities(any())) - .willReturn((Collection) AuthorityUtils.createAuthorityList("ROLE_OAUTH2_USER")); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("code", "code123"); - params.add("state", authorizationRequest.getState()); - this.mvc.perform(get("/login/oauth2/code/github-login").params(params)).andExpect(status().is2xxSuccessful()); - ArgumentCaptor authenticationCaptor = ArgumentCaptor.forClass(Authentication.class); - verify(this.authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), authenticationCaptor.capture()); - Authentication authentication = authenticationCaptor.getValue(); - assertThat(authentication.getPrincipal()).isInstanceOf(OAuth2User.class); - SecurityAssertions.assertThat(authentication) - .roles() - .hasSize(1) - .first() - .isInstanceOf(SimpleGrantedAuthority.class) - .hasToString("ROLE_OAUTH2_USER"); - // re-setup for OIDC test - attributes = new HashMap<>(); - attributes.put(OAuth2ParameterNames.REGISTRATION_ID, "google-login"); - authorizationRequest = TestOAuth2AuthorizationRequests.oidcRequest().attributes(attributes).build(); - given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) - .willReturn(authorizationRequest); - accessTokenResponse = TestOAuth2AccessTokenResponses.oidcAccessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); - Jwt jwt = TestJwts.user(); - given(this.jwtDecoderFactory.createDecoder(any())).willReturn((token) -> jwt); - DefaultOidcUser oidcUser = TestOidcUsers.create(); - given(this.oidcUserService.loadUser(any())).willReturn(oidcUser); - given(this.userAuthoritiesMapper.mapAuthorities(any())) - .willReturn((Collection) AuthorityUtils.createAuthorityList("ROLE_OIDC_USER")); - // @formatter:off - this.mvc.perform(get("/login/oauth2/code/google-login").params(params)) - .andExpect(status().is2xxSuccessful()); - // @formatter:on - authenticationCaptor = ArgumentCaptor.forClass(Authentication.class); - verify(this.authenticationSuccessHandler, times(2)).onAuthenticationSuccess(any(), any(), - authenticationCaptor.capture()); - authentication = authenticationCaptor.getValue(); - assertThat(authentication.getPrincipal()).isInstanceOf(OidcUser.class); - assertThat(authentication.getAuthorities()).hasSize(1); - assertThat(authentication.getAuthorities()).first() - .isInstanceOf(SimpleGrantedAuthority.class) - .hasToString("ROLE_OIDC_USER"); - } - - // gh-5488 - @Test - public void requestWhenCustomLoginProcessingUrlThenProcessAuthentication() throws Exception { - this.spring.configLocations(this.xml("MultiClientRegistration-WithCustomLoginProcessingUrl")).autowire(); - Map attributes = new HashMap<>(); - attributes.put(OAuth2ParameterNames.REGISTRATION_ID, "github-login"); - OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() - .attributes(attributes) - .build(); - given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) - .willReturn(authorizationRequest); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); - OAuth2User oauth2User = TestOAuth2Users.create(); - given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("code", "code123"); - params.add("state", authorizationRequest.getState()); - // @formatter:off - this.mvc.perform(get("/login/oauth2/github-login").params(params)) - .andExpect(status().is2xxSuccessful()); - // @formatter:on - ArgumentCaptor authenticationCaptor = ArgumentCaptor.forClass(Authentication.class); - verify(this.authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), authenticationCaptor.capture()); - Authentication authentication = authenticationCaptor.getValue(); - assertThat(authentication.getPrincipal()).isInstanceOf(OAuth2User.class); - } - - // gh-5521 - @Test - public void requestWhenCustomAuthorizationRequestResolverThenCalled() throws Exception { - this.spring.configLocations(this.xml("SingleClientRegistration-WithCustomAuthorizationRequestResolver")) - .autowire(); - // @formatter:off - this.mvc.perform(get("/oauth2/authorization/google-login")) - .andExpect(status().is3xxRedirection()); - // @formatter:on - verify(this.authorizationRequestResolver).resolve(any()); - } - - @Test - public void requestWhenCustomAuthorizationRedirectStrategyThenCalled() throws Exception { - this.spring.configLocations(this.xml("SingleClientRegistration-WithCustomAuthorizationRedirectStrategy")) - .autowire(); - // @formatter:off - this.mvc.perform(get("/oauth2/authorization/google-login")) - .andExpect(status().isOk()); - // @formatter:on - verify(this.authorizationRedirectStrategy).sendRedirect(any(), any(), anyString()); - } - - // gh-5347 - @Test - public void requestWhenMultiClientRegistrationThenRedirectDefaultLoginPage() throws Exception { - this.spring.configLocations(this.xml("MultiClientRegistration")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/login")); - // @formatter:on - } - - @Test - public void requestWhenCustomLoginPageThenRedirectCustomLoginPage() throws Exception { - this.spring.configLocations(this.xml("SingleClientRegistration-WithCustomLoginPage")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/custom-login")); - // @formatter:on - } - - // gh-6802 - @Test - public void requestWhenSingleClientRegistrationAndFormLoginConfiguredThenRedirectDefaultLoginPage() - throws Exception { - this.spring.configLocations(this.xml("SingleClientRegistration-WithFormLogin")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/login")); - // @formatter:on - } - - @Test - public void requestWhenCustomClientRegistrationRepositoryThenCalled() throws Exception { - this.spring.configLocations(this.xml("WithCustomClientRegistrationRepository")).autowire(); - ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); - given(this.clientRegistrationRepository.findByRegistrationId(any())).willReturn(clientRegistration); - Map attributes = new HashMap<>(); - attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); - OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() - .attributes(attributes) - .build(); - given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) - .willReturn(authorizationRequest); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); - OAuth2User oauth2User = TestOAuth2Users.create(); - given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("code", "code123"); - params.add("state", authorizationRequest.getState()); - this.mvc.perform(get("/login/oauth2/code/" + clientRegistration.getRegistrationId()).params(params)); - verify(this.clientRegistrationRepository).findByRegistrationId(clientRegistration.getRegistrationId()); - } - - @Test - public void requestWhenCustomAuthorizedClientRepositoryThenCalled() throws Exception { - this.spring.configLocations(this.xml("WithCustomAuthorizedClientRepository")).autowire(); - ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); - given(this.clientRegistrationRepository.findByRegistrationId(any())).willReturn(clientRegistration); - Map attributes = new HashMap<>(); - attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); - OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() - .attributes(attributes) - .build(); - given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) - .willReturn(authorizationRequest); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); - OAuth2User oauth2User = TestOAuth2Users.create(); - given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("code", "code123"); - params.add("state", authorizationRequest.getState()); - this.mvc.perform(get("/login/oauth2/code/" + clientRegistration.getRegistrationId()).params(params)); - verify(this.authorizedClientRepository).saveAuthorizedClient(any(), any(), any(), any()); - } - - @Test - public void requestWhenCustomAuthorizedClientServiceThenCalled() throws Exception { - this.spring.configLocations(this.xml("WithCustomAuthorizedClientService")).autowire(); - ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); - given(this.clientRegistrationRepository.findByRegistrationId(any())).willReturn(clientRegistration); - Map attributes = new HashMap<>(); - attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); - OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() - .attributes(attributes) - .build(); - given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) - .willReturn(authorizationRequest); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); - OAuth2User oauth2User = TestOAuth2Users.create(); - given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("code", "code123"); - params.add("state", authorizationRequest.getState()); - this.mvc.perform(get("/login/oauth2/code/" + clientRegistration.getRegistrationId()).params(params)); - verify(this.authorizedClientService).saveAuthorizedClient(any(), any()); - } - - @Test - public void requestWhenCustomSecurityContextHolderStrategyThenCalled() throws Exception { - this.spring.configLocations(this.xml("WithCustomSecurityContextHolderStrategy")).autowire(); - ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); - given(this.clientRegistrationRepository.findByRegistrationId(any())).willReturn(clientRegistration); - Map attributes = new HashMap<>(); - attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); - OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() - .attributes(attributes) - .build(); - given(this.authorizationRequestRepository.removeAuthorizationRequest(any(), any())) - .willReturn(authorizationRequest); - OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build(); - given(this.accessTokenResponseClient.getTokenResponse(any())).willReturn(accessTokenResponse); - OAuth2User oauth2User = TestOAuth2Users.create(); - given(this.oauth2UserService.loadUser(any())).willReturn(oauth2User); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("code", "code123"); - params.add("state", authorizationRequest.getState()); - this.mvc.perform(get("/login/oauth2/code/" + clientRegistration.getRegistrationId()).params(params)); - verify(this.securityContextHolderStrategy, atLeastOnce()).getContext(); - } - - @WithMockUser - @Test - public void requestWhenAuthorizedClientFoundThenMethodArgumentResolved() throws Exception { - this.spring.configLocations(xml("AuthorizedClientArgumentResolver")).autowire(); - ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google-login"); - OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, "user", - TestOAuth2AccessTokens.noScopes()); - MockHttpServletRequest request = new MockHttpServletRequest(); - MockHttpServletResponse response = new MockHttpServletResponse(); - this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, null, request, response); - // @formatter:off - this.mvc.perform(get("/authorized-client").session((MockHttpSession) request.getSession())) - .andExpect(status().isOk()) - .andExpect(content().string("resolved")); - // @formatter:on - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - @RestController - static class AuthorizedClientController { - - @GetMapping("/authorized-client") - String authorizedClient(Model model, - @RegisteredOAuth2AuthorizedClient("google-login") OAuth2AuthorizedClient authorizedClient) { - return (authorizedClient != null) ? "resolved" : "not-resolved"; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java deleted file mode 100644 index b8bb4ec3cc4..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests.java +++ /dev/null @@ -1,1156 +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.http; - -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; -import java.security.interfaces.RSAPublicKey; -import java.time.Clock; -import java.time.Instant; -import java.time.ZoneId; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; -import java.util.stream.Collectors; - -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.JWSHeader; -import com.nimbusds.jose.JWSObject; -import com.nimbusds.jose.Payload; -import com.nimbusds.jose.crypto.RSASSASigner; -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jose.util.JSONObjectUtils; -import jakarta.servlet.http.HttpServletRequest; -import net.minidev.json.JSONObject; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.hamcrest.core.AllOf; -import org.hamcrest.core.StringContains; -import org.hamcrest.core.StringEndsWith; -import org.hamcrest.core.StringStartsWith; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mockito; -import org.w3c.dom.Element; - -import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate; -import org.springframework.beans.factory.xml.ParserContext; -import org.springframework.beans.factory.xml.XmlReaderContext; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.convert.converter.Converter; -import org.springframework.core.io.ClassPathResource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; -import org.springframework.security.authentication.AuthenticationManagerResolver; -import org.springframework.security.authentication.AuthenticationServiceException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParser.JwtBeanDefinitionParser; -import org.springframework.security.config.http.OAuth2ResourceServerBeanDefinitionParser.OpaqueTokenBeanDefinitionParser; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2TokenValidator; -import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; -import org.springframework.security.oauth2.jose.TestKeys; -import org.springframework.security.oauth2.jwt.BadJwtException; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtClaimNames; -import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; -import org.springframework.security.oauth2.jwt.TestJwts; -import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; -import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter; -import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector; -import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector; -import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; -import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.ResultMatcher; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.client.RestOperations; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Josh Cummings - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class OAuth2ResourceServerBeanDefinitionParserTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParserTests"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Autowired(required = false) - MockWebServer web; - - @Test - public void getWhenValidBearerTokenThenAcceptsRequest() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) - .andExpect(status().isNotFound()); - // @formatter:on - } - - @Test - public void getWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("JwtCustomSecurityContextHolderStrategy")).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) - .andExpect(status().isNotFound()); - // @formatter:on - SecurityContextHolderStrategy securityContextHolderStrategy = this.spring.getContext() - .getBean(SecurityContextHolderStrategy.class); - verify(securityContextHolderStrategy, atLeastOnce()).getContext(); - } - - @Test - public void getWhenUsingJwkSetUriThenAcceptsRequest() throws Exception { - this.spring.configLocations(xml("WebServer"), xml("JwkSetUri")).autowire(); - mockWebServer(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) - .andExpect(status().isNotFound()); - // @formatter:on - } - - @Test - public void getWhenExpiredBearerTokenThenInvalidToken() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("Expired"); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); - // @formatter:on - } - - @Test - public void getWhenBadJwkEndpointThen500() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); - mockJwksRestOperations("malformed"); - String token = this.token("ValidNoScopes"); - // @formatter:off - assertThatExceptionOfType(AuthenticationServiceException.class) - .isThrownBy(() -> this.mvc.perform(get("/").header("Authorization", "Bearer " + token))); - // @formatter:on - } - - @Test - public void getWhenUnavailableJwkEndpointThen500() throws Exception { - this.spring.configLocations(xml("WebServer"), xml("JwkSetUri")).autowire(); - this.web.shutdown(); - String token = this.token("ValidNoScopes"); - // @formatter:off - assertThatExceptionOfType(AuthenticationServiceException.class) - .isThrownBy(() -> this.mvc.perform(get("/").header("Authorization", "Bearer " + token))); - // @formatter:on - } - - @Test - public void getWhenMalformedBearerTokenThenInvalidToken() throws Exception { - this.spring.configLocations(xml("JwkSetUri")).autowire(); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer an\"invalid\"token")) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("Bearer token is malformed")); - // @formatter:on - } - - @Test - public void getWhenMalformedPayloadThenInvalidToken() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("MalformedPayload"); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt: Malformed payload")); - // @formatter:on - } - - @Test - public void getWhenUnsignedBearerTokenThenInvalidToken() throws Exception { - this.spring.configLocations(xml("JwkSetUri")).autowire(); - String token = this.token("Unsigned"); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("Unsupported algorithm of none")); - // @formatter:on - } - - @Test - public void getWhenBearerTokenBeforeNotBeforeThenInvalidToken() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); - this.mockJwksRestOperations(jwks("Default")); - String token = this.token("TooEarly"); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); - // @formatter:on - } - - @Test - public void getWhenBearerTokenInTwoPlacesThenInvalidRequest() throws Exception { - this.spring.configLocations(xml("JwkSetUri")).autowire(); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer token").param("access_token", "token")) - .andExpect(status().isBadRequest()) - .andExpect(invalidRequestHeader("Found multiple bearer tokens in the request")); - // @formatter:on - } - - @Test - public void getWhenBearerTokenInTwoParametersThenInvalidRequest() throws Exception { - this.spring.configLocations(xml("JwkSetUri")).autowire(); - MultiValueMap params = new LinkedMultiValueMap<>(); - params.add("access_token", "token1"); - params.add("access_token", "token2"); - // @formatter:off - this.mvc.perform(get("/").params(params)) - .andExpect(status().isBadRequest()) - .andExpect(invalidRequestHeader("Found multiple bearer tokens in the request")); - // @formatter:on - } - - @Test - public void postWhenBearerTokenAsFormParameterThenIgnoresToken() throws Exception { - this.spring.configLocations(xml("JwkSetUri")).autowire(); - this.mvc.perform(post("/") // engage csrf - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) - .param("access_token", "token")) - .andExpect(status().isForbidden()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer")); // different - // from - // DSL - } - - @Test - public void getWhenNoBearerTokenThenUnauthorized() throws Exception { - this.spring.configLocations(xml("JwkSetUri")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\"")); - // @formatter:on - } - - @Test - public void getWhenSufficientlyScopedBearerTokenThenAcceptsRequest() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidMessageReadScope"); - // @formatter:off - this.mvc.perform(get("/requires-read-scope").header("Authorization", "Bearer " + token)) - .andExpect(status().isNotFound()); - // @formatter:on - } - - @Test - public void getWhenInsufficientScopeThenInsufficientScopeError() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/requires-read-scope").header("Authorization", "Bearer " + token)) - .andExpect(status().isForbidden()) - .andExpect(insufficientScopeHeader()); - // @formatter:on - } - - @Test - public void getWhenInsufficientScpThenInsufficientScopeError() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidMessageWriteScp"); - // @formatter:off - this.mvc.perform(get("/requires-read-scope").header("Authorization", "Bearer " + token)) - .andExpect(status().isForbidden()) - .andExpect(insufficientScopeHeader()); - // @formatter:on - } - - @Test - public void getWhenAuthorizationServerHasNoMatchingKeyThenInvalidToken() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); - mockJwksRestOperations(jwks("Empty")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); - // @formatter:on - } - - @Test - public void getWhenAuthorizationServerHasMultipleMatchingKeysThenOk() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); - mockJwksRestOperations(jwks("TwoKeys")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/authenticated").header("Authorization", "Bearer " + token)) - .andExpect(status().isNotFound()); - // @formatter:on - } - - @Test - public void getWhenKeyMatchesByKidThenOk() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); - mockJwksRestOperations(jwks("TwoKeys")); - String token = this.token("Kid"); - // @formatter:off - this.mvc.perform(get("/authenticated").header("Authorization", "Bearer " + token)) - .andExpect(status().isNotFound()); - // @formatter:on - } - - @Test - public void postWhenValidBearerTokenAndNoCsrfTokenThenOk() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(post("/authenticated").header("Authorization", "Bearer " + token)) - .andExpect(status().isNotFound()); - // @formatter:on - } - - @Test - public void postWhenNoBearerTokenThenCsrfDenies() throws Exception { - this.spring.configLocations(xml("JwkSetUri")).autowire(); - // @formatter:off - this.mvc.perform(post("/authenticated")) - .andExpect(status().isForbidden()) - // different from DSL - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer")); - // @formatter:on - } - - @Test - public void postWhenExpiredBearerTokenAndNoCsrfThenInvalidToken() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("Expired"); - // @formatter:off - this.mvc.perform(post("/authenticated").header("Authorization", "Bearer " + token)) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("An error occurred while attempting to decode the Jwt")); - // @formatter:on - } - - @Test - public void requestWhenJwtThenSessionIsNotCreated() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("Jwt")).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - MvcResult result = this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) - .andExpect(status().isNotFound()) - .andReturn(); - // @formatter:on - assertThat(result.getRequest().getSession(false)).isNull(); - } - - @Test - public void requestWhenIntrospectionThenSessionIsNotCreated() throws Exception { - this.spring.configLocations(xml("WebServer"), xml("IntrospectionUri")).autowire(); - mockWebServer(json("Active")); - // @formatter:off - MvcResult result = this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token")) - .andExpect(status().isNotFound()) - .andReturn(); - // @formatter:on - assertThat(result.getRequest().getSession(false)).isNull(); - } - - @Test - public void requestWhenNoBearerTokenThenSessionIsCreated() throws Exception { - this.spring.configLocations(xml("JwkSetUri")).autowire(); - // @formatter:off - MvcResult result = this.mvc.perform(get("/")) - .andExpect(status().isUnauthorized()) - .andReturn(); - // @formatter:on - assertThat(result.getRequest().getSession(false)).isNotNull(); - } - - @Test - public void requestWhenSessionManagementConfiguredThenUses() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("AlwaysSessionCreation")).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - MvcResult result = this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) - .andExpect(status().isNotFound()) - .andReturn(); - // @formatter:on - assertThat(result.getRequest().getSession(false)).isNotNull(); - } - - @Test - public void getWhenCustomBearerTokenResolverThenUses() throws Exception { - this.spring.configLocations(xml("MockBearerTokenResolver"), xml("MockJwtDecoder"), xml("BearerTokenResolver")) - .autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode("token")).willReturn(TestJwts.jwt().build()); - BearerTokenResolver bearerTokenResolver = this.spring.getContext().getBean(BearerTokenResolver.class); - given(bearerTokenResolver.resolve(any(HttpServletRequest.class))).willReturn("token"); - this.mvc.perform(get("/")).andExpect(status().isNotFound()); - verify(decoder).decode("token"); - verify(bearerTokenResolver).resolve(any(HttpServletRequest.class)); - } - - @Test - public void getWhenCustomAuthenticationConverterThenUses() throws Exception { - this.spring - .configLocations(xml("MockAuthenticationConverter"), xml("MockJwtDecoder"), xml("AuthenticationConverter")) - .autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode("token")).willReturn(TestJwts.jwt().build()); - AuthenticationConverter authenticationConverter = this.spring.getContext() - .getBean(AuthenticationConverter.class); - given(authenticationConverter.convert(any(HttpServletRequest.class))) - .willReturn(new BearerTokenAuthenticationToken("token")); - - this.mvc.perform(get("/")).andExpect(status().isNotFound()); - - verify(decoder).decode("token"); - verify(authenticationConverter).convert(any(HttpServletRequest.class)); - } - - @Test - public void requestWhenBearerTokenResolverAllowsRequestBodyThenEitherHeaderOrRequestBodyIsAccepted() - throws Exception { - this.spring.configLocations(xml("MockJwtDecoder"), xml("AllowBearerTokenInBody")).autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willReturn(TestJwts.jwt().build()); - // @formatter:off - this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token")) - .andExpect(status().isNotFound()); - this.mvc.perform(post("/authenticated").header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE).param("access_token", "token")) - .andExpect(status().isNotFound()); - // @formatter:on - } - - @Test - public void requestWhenBearerTokenResolverAllowsQueryParameterThenEitherHeaderOrQueryParameterIsAccepted() - throws Exception { - this.spring.configLocations(xml("MockJwtDecoder"), xml("AllowBearerTokenInQuery")).autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willReturn(TestJwts.jwt().build()); - // @formatter:off - this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token")) - .andExpect(status().isNotFound()); - this.mvc.perform(get("/authenticated").param("access_token", "token")) - .andExpect(status().isNotFound()); - // @formatter:on - verify(decoder, times(2)).decode("token"); - } - - @Test - public void requestWhenBearerTokenResolverAllowsRequestBodyAndRequestContainsTwoTokensThenInvalidRequest() - throws Exception { - this.spring.configLocations(xml("MockJwtDecoder"), xml("AllowBearerTokenInBody")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = post("/authenticated") - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) - .param("access_token", "token") - .header("Authorization", "Bearer token") - .with(csrf()); - this.mvc.perform(request) - .andExpect(status().isBadRequest()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("invalid_request"))); - // @formatter:on - } - - @Test - public void requestWhenBearerTokenResolverAllowsQueryParameterAndRequestContainsTwoTokensThenInvalidRequest() - throws Exception { - this.spring.configLocations(xml("MockJwtDecoder"), xml("AllowBearerTokenInQuery")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = get("/authenticated") - .header("Authorization", "Bearer token") - .param("access_token", "token"); - this.mvc.perform(request) - .andExpect(status().isBadRequest()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("invalid_request"))); - // @formatter:on - } - - @Test - public void requestWhenCustomJwtDecoderThenUsed() throws Exception { - this.spring.configLocations(xml("MockJwtDecoder"), xml("Jwt")).autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willReturn(TestJwts.jwt().build()); - this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token")) - .andExpect(status().isNotFound()); - verify(decoder).decode("token"); - } - - @Test - public void configureWhenDecoderAndJwkSetUriThenException() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(xml("JwtDecoderAndJwkSetUri")).autowire()); - } - - @Test - public void configureWhenAuthenticationConverterAndJwkSetUriThenException() { - assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy( - () -> this.spring.configLocations(xml("AuthenticationConverterAndBearerTokenResolver")).autowire()); - } - - @Test - public void requestWhenRealmNameConfiguredThenUsesOnUnauthenticated() throws Exception { - this.spring.configLocations(xml("MockJwtDecoder"), xml("AuthenticationEntryPoint")).autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - Mockito.when(decoder.decode(anyString())).thenThrow(BadJwtException.class); - // @formatter:off - this.mvc.perform(get("/authenticated").header("Authorization", "Bearer invalid_token")) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer realm=\"myRealm\""))); - // @formatter:on - } - - @Test - public void requestWhenRealmNameConfiguredThenUsesOnAccessDenied() throws Exception { - this.spring.configLocations(xml("MockJwtDecoder"), xml("AccessDeniedHandler")).autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willReturn(TestJwts.jwt().build()); - // @formatter:off - this.mvc.perform(get("/authenticated").header("Authorization", "Bearer insufficiently_scoped")) - .andExpect(status().isForbidden()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer realm=\"myRealm\""))); - // @formatter:on - } - - @Test - public void requestWhenCustomJwtValidatorFailsThenCorrespondingErrorMessage() throws Exception { - this.spring.configLocations(xml("MockJwtValidator"), xml("Jwt")).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - OAuth2TokenValidator jwtValidator = this.spring.getContext().getBean(OAuth2TokenValidator.class); - OAuth2Error error = new OAuth2Error("custom-error", "custom-description", "custom-uri"); - given(jwtValidator.validate(any(Jwt.class))).willReturn(OAuth2TokenValidatorResult.failure(error)); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("custom-description"))); - // @formatter:on - } - - @Test - public void requestWhenClockSkewSetThenTimestampWindowRelaxedAccordingly() throws Exception { - this.spring.configLocations(xml("UnexpiredJwtClockSkew"), xml("Jwt")).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ExpiresAt4687177990"); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) - .andExpect(status().isNotFound()); - // @formatter:on - } - - @Test - public void requestWhenClockSkewSetButJwtStillTooLateThenReportsExpired() throws Exception { - this.spring.configLocations(xml("ExpiredJwtClockSkew"), xml("Jwt")).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ExpiresAt4687177990"); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("Jwt expired at")); - // @formatter:on - } - - @Test - public void requestWhenJwtAuthenticationConverterThenUsed() throws Exception { - this.spring - .configLocations(xml("MockJwtDecoder"), xml("MockJwtAuthenticationConverter"), - xml("JwtAuthenticationConverter")) - .autowire(); - Converter jwtAuthenticationConverter = (Converter) this.spring - .getContext() - .getBean("jwtAuthenticationConverter"); - given(jwtAuthenticationConverter.convert(any(Jwt.class))) - .willReturn(new JwtAuthenticationToken(TestJwts.jwt().build(), Collections.emptyList())); - JwtDecoder jwtDecoder = this.spring.getContext().getBean(JwtDecoder.class); - given(jwtDecoder.decode(anyString())).willReturn(TestJwts.jwt().build()); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer token")) - .andExpect(status().isNotFound()); - // @formatter:on - verify(jwtAuthenticationConverter).convert(any(Jwt.class)); - } - - @Test - public void requestWhenUsingPublicKeyAndValidTokenThenAuthenticates() throws Exception { - this.spring.configLocations(xml("SingleKey"), xml("Jwt")).autowire(); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) - .andExpect(status().isNotFound()); - // @formatter:on - } - - @Test - public void requestWhenUsingPublicKeyAndSignatureFailsThenReturnsInvalidToken() throws Exception { - this.spring.configLocations(xml("SingleKey"), xml("Jwt")).autowire(); - String token = this.token("WrongSignature"); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) - .andExpect(invalidTokenHeader("signature")); - // @formatter:on - } - - @Test - public void requestWhenUsingPublicKeyAlgorithmDoesNotMatchThenReturnsInvalidToken() throws Exception { - this.spring.configLocations(xml("SingleKey"), xml("Jwt")).autowire(); - String token = this.token("WrongAlgorithm"); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer " + token)) - .andExpect(invalidTokenHeader("algorithm")); - // @formatter:on - } - - @Test - public void getWhenIntrospectingThenOk() throws Exception { - this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueToken")).autowire(); - mockJsonRestOperations(json("Active")); - // @formatter:off - this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token")) - .andExpect(status().isNotFound()); - // @formatter:on - } - - @Test - public void configureWhenIntrospectingWithAuthenticationConverterThenUses() throws Exception { - this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueTokenAndAuthenticationConverter")) - .autowire(); - mockJsonRestOperations(json("Active")); - OpaqueTokenAuthenticationConverter converter = bean(OpaqueTokenAuthenticationConverter.class); - given(converter.convert(any(), any())).willReturn(new TestingAuthenticationToken("user", "pass", "app")); - // @formatter:off - this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token")) - .andExpect(status().isNotFound()); - // @formatter:on - verify(converter).convert(any(), any()); - } - - @Test - public void getWhenIntrospectionFailsThenUnauthorized() throws Exception { - this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueToken")).autowire(); - mockJsonRestOperations(json("Inactive")); - // @formatter:off - MockHttpServletRequestBuilder request = get("/") - .header("Authorization", "Bearer token"); - this.mvc.perform(request) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("Provided token isn't active"))); - // @formatter:on - } - - @Test - public void getWhenIntrospectionLacksScopeThenForbidden() throws Exception { - this.spring.configLocations(xml("OpaqueTokenRestOperations"), xml("OpaqueToken")).autowire(); - mockJsonRestOperations(json("ActiveNoScopes")); - // @formatter:off - this.mvc.perform(get("/requires-read-scope").header("Authorization", "Bearer token")) - .andExpect(status().isForbidden()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("scope"))); - // @formatter:on - } - - @Test - public void configureWhenOnlyIntrospectionUrlThenException() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(xml("OpaqueTokenHalfConfigured")).autowire()); - } - - @Test - public void configureWhenIntrospectorAndIntrospectionUriThenError() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(xml("OpaqueTokenAndIntrospectionUri")).autowire()); - } - - @Test - public void getWhenAuthenticationManagerResolverThenUses() throws Exception { - this.spring.configLocations(xml("AuthenticationManagerResolver")).autowire(); - AuthenticationManagerResolver authenticationManagerResolver = this.spring.getContext() - .getBean(AuthenticationManagerResolver.class); - given(authenticationManagerResolver.resolve(any(HttpServletRequest.class))).willReturn( - (authentication) -> new JwtAuthenticationToken(TestJwts.jwt().build(), Collections.emptyList())); - // @formatter:off - this.mvc.perform(get("/").header("Authorization", "Bearer token")) - .andExpect(status().isNotFound()); - // @formatter:on - verify(authenticationManagerResolver).resolve(any(HttpServletRequest.class)); - } - - @Test - public void getWhenMultipleIssuersThenUsesIssuerClaimToDifferentiate() throws Exception { - this.spring.configLocations(xml("WebServer"), xml("MultipleIssuers")).autowire(); - MockWebServer server = this.spring.getContext().getBean(MockWebServer.class); - String metadata = "{\n" + " \"issuer\": \"%s\", \n" + " \"jwks_uri\": \"%s/.well-known/jwks.json\" \n" - + "}"; - String jwkSet = jwkSet(); - String issuerOne = server.url("/issuerOne").toString(); - String issuerTwo = server.url("/issuerTwo").toString(); - String issuerThree = server.url("/issuerThree").toString(); - String jwtOne = jwtFromIssuer(issuerOne); - String jwtTwo = jwtFromIssuer(issuerTwo); - String jwtThree = jwtFromIssuer(issuerThree); - mockWebServer(String.format(metadata, issuerOne, issuerOne)); - mockWebServer(jwkSet); - // @formatter:off - this.mvc.perform(get("/authenticated").header("Authorization", "Bearer " + jwtOne)) - .andExpect(status().isNotFound()); - // @formatter:on - mockWebServer(String.format(metadata, issuerTwo, issuerTwo)); - mockWebServer(jwkSet); - // @formatter:off - this.mvc.perform(get("/authenticated").header("Authorization", "Bearer " + jwtTwo)) - .andExpect(status().isNotFound()); - // @formatter:on - mockWebServer(String.format(metadata, issuerThree, issuerThree)); - mockWebServer(jwkSet); - // @formatter:off - this.mvc.perform(get("/authenticated").header("Authorization", "Bearer " + jwtThree)) - .andExpect(status().isUnauthorized()) - .andExpect(invalidTokenHeader("Invalid issuer")); - // @formatter:on - } - - @Test - public void requestWhenBasicAndResourceServerEntryPointsThenBearerTokenPresides() throws Exception { - // different from DSL - this.spring.configLocations(xml("MockJwtDecoder"), xml("BasicAndResourceServer")).autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willThrow(BadJwtException.class); - // @formatter:off - this.mvc.perform(get("/authenticated").with(httpBasic("some", "user"))) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Basic"))); - this.mvc.perform(get("/authenticated")) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer"))); - this.mvc.perform(get("/authenticated").header("Authorization", "Bearer invalid_token")) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer"))); - // @formatter:on - } - - @Test - public void requestWhenFormLoginAndResourceServerEntryPointsThenSessionCreatedByRequest() throws Exception { - // different from DSL - this.spring.configLocations(xml("MockJwtDecoder"), xml("FormAndResourceServer")).autowire(); - JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class); - given(decoder.decode(anyString())).willThrow(BadJwtException.class); - MvcResult result = this.mvc.perform(get("/authenticated")).andExpect(status().isUnauthorized()).andReturn(); - assertThat(result.getRequest().getSession(false)).isNotNull(); - // @formatter:off - result = this.mvc.perform(get("/authenticated").header("Authorization", "Bearer token")) - .andExpect(status().isUnauthorized()) - .andReturn(); - // @formatter:on - assertThat(result.getRequest().getSession(false)).isNull(); - } - - @Test - public void getWhenAlsoUsingHttpBasicThenCorrectProviderEngages() throws Exception { - this.spring.configLocations(xml("JwtRestOperations"), xml("BasicAndResourceServer")).autowire(); - mockJwksRestOperations(jwks("Default")); - String token = this.token("ValidNoScopes"); - // @formatter:off - this.mvc.perform(get("/authenticated").header("Authorization", "Bearer " + token)) - .andExpect(status().isNotFound()); - this.mvc.perform(get("/authenticated").with(httpBasic("user", "password"))) - .andExpect(status().isNotFound()); - // @formatter:on - } - - @Test - public void configuredWhenMissingJwtAuthenticationProviderThenWiringException() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(xml("Jwtless")).autowire()) - .withMessageContaining("Please select one"); - } - - @Test - public void configureWhenMissingJwkSetUriThenWiringException() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(xml("JwtHalfConfigured")).autowire()) - .withMessageContaining("Please specify either"); - } - - @Test - public void configureWhenUsingBothAuthenticationManagerResolverAndJwtThenException() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy( - () -> this.spring.configLocations(xml("AuthenticationManagerResolverPlusOtherConfig")).autowire()) - .withMessageContaining("authentication-manager-resolver-ref"); - } - - @Test - public void validateConfigurationWhenMoreThanOneResourceServerModeThenError() { - OAuth2ResourceServerBeanDefinitionParser parser = new OAuth2ResourceServerBeanDefinitionParser(null, null, null, - null, null, null); - Element element = mock(Element.class); - given(element.hasAttribute(OAuth2ResourceServerBeanDefinitionParser.AUTHENTICATION_MANAGER_RESOLVER_REF)) - .willReturn(true); - Element child = mock(Element.class); - ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); - parser.validateConfiguration(element, child, null, pc); - verify(pc.getReaderContext()).error(anyString(), eq(element)); - reset(pc.getReaderContext()); - parser.validateConfiguration(element, null, child, pc); - verify(pc.getReaderContext()).error(anyString(), eq(element)); - } - - @Test - public void validateConfigurationWhenNoResourceServerModeThenError() { - OAuth2ResourceServerBeanDefinitionParser parser = new OAuth2ResourceServerBeanDefinitionParser(null, null, null, - null, null, null); - Element element = mock(Element.class); - given(element.hasAttribute(OAuth2ResourceServerBeanDefinitionParser.AUTHENTICATION_MANAGER_RESOLVER_REF)) - .willReturn(false); - ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); - parser.validateConfiguration(element, null, null, pc); - verify(pc.getReaderContext()).error(anyString(), eq(element)); - } - - @Test - public void validateConfigurationWhenBothJwtAttributesThenError() { - JwtBeanDefinitionParser parser = new JwtBeanDefinitionParser(); - Element element = mock(Element.class); - given(element.hasAttribute(JwtBeanDefinitionParser.JWK_SET_URI)).willReturn(true); - given(element.hasAttribute(JwtBeanDefinitionParser.DECODER_REF)).willReturn(true); - ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); - parser.validateConfiguration(element, pc); - verify(pc.getReaderContext()).error(anyString(), eq(element)); - } - - @Test - public void validateConfigurationWhenNoJwtAttributesThenError() { - JwtBeanDefinitionParser parser = new JwtBeanDefinitionParser(); - Element element = mock(Element.class); - given(element.hasAttribute(JwtBeanDefinitionParser.JWK_SET_URI)).willReturn(false); - given(element.hasAttribute(JwtBeanDefinitionParser.DECODER_REF)).willReturn(false); - ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); - parser.validateConfiguration(element, pc); - verify(pc.getReaderContext()).error(anyString(), eq(element)); - } - - @Test - public void validateConfigurationWhenBothOpaqueTokenModesThenError() { - OpaqueTokenBeanDefinitionParser parser = new OpaqueTokenBeanDefinitionParser(); - Element element = mock(Element.class); - given(element.hasAttribute(OpaqueTokenBeanDefinitionParser.INTROSPECTION_URI)).willReturn(true); - given(element.hasAttribute(OpaqueTokenBeanDefinitionParser.INTROSPECTOR_REF)).willReturn(true); - ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); - parser.validateConfiguration(element, pc); - verify(pc.getReaderContext()).error(anyString(), eq(element)); - } - - @Test - public void validateConfigurationWhenNoOpaqueTokenModeThenError() { - OpaqueTokenBeanDefinitionParser parser = new OpaqueTokenBeanDefinitionParser(); - Element element = mock(Element.class); - given(element.hasAttribute(OpaqueTokenBeanDefinitionParser.INTROSPECTION_URI)).willReturn(false); - given(element.hasAttribute(OpaqueTokenBeanDefinitionParser.INTROSPECTOR_REF)).willReturn(false); - ParserContext pc = new ParserContext(mock(XmlReaderContext.class), mock(BeanDefinitionParserDelegate.class)); - parser.validateConfiguration(element, pc); - verify(pc.getReaderContext()).error(anyString(), eq(element)); - } - - private static ResultMatcher invalidRequestHeader(String message) { - return header().string(HttpHeaders.WWW_AUTHENTICATE, - AllOf.allOf(new StringStartsWith("Bearer " + "error=\"invalid_request\", " + "error_description=\""), - new StringContains(message), - new StringContains(", " + "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""), - new StringEndsWith( - ", " + "resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\""))); - } - - private static ResultMatcher invalidTokenHeader(String message) { - return header().string(HttpHeaders.WWW_AUTHENTICATE, - AllOf.allOf(new StringStartsWith("Bearer " + "error=\"invalid_token\", " + "error_description=\""), - new StringContains(message), - new StringContains(", " + "error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""), - new StringEndsWith( - ", " + "resource_metadata=\"http://localhost/.well-known/oauth-protected-resource\""))); - } - - private static ResultMatcher insufficientScopeHeader() { - return header().string(HttpHeaders.WWW_AUTHENTICATE, "Bearer " + "error=\"insufficient_scope\"" - + ", error_description=\"The request requires higher privileges than provided by the access token.\"" - + ", error_uri=\"https://tools.ietf.org/html/rfc6750#section-3.1\""); - } - - private String jwkSet() { - return new JWKSet(new RSAKey.Builder(TestKeys.DEFAULT_PUBLIC_KEY).keyID("1").build()).toString(); - } - - private String jwtFromIssuer(String issuer) throws Exception { - Map claims = new HashMap<>(); - claims.put(JwtClaimNames.ISS, issuer); - claims.put(JwtClaimNames.SUB, "test-subject"); - claims.put("scope", "message:read"); - JWSObject jws = new JWSObject(new JWSHeader.Builder(JWSAlgorithm.RS256).keyID("1").build(), - new Payload(new JSONObject(claims))); - jws.sign(new RSASSASigner(TestKeys.DEFAULT_PRIVATE_KEY)); - return jws.serialize(); - } - - private void mockWebServer(String response) { - this.web.enqueue(new MockResponse().setResponseCode(200) - .setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) - .setBody(response)); - } - - private void mockJwksRestOperations(String response) { - RestOperations rest = this.spring.getContext().getBean(RestOperations.class); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - ResponseEntity entity = new ResponseEntity<>(response, headers, HttpStatus.OK); - given(rest.exchange(any(RequestEntity.class), eq(String.class))).willReturn(entity); - } - - private void mockJsonRestOperations(String response) { - try { - RestOperations rest = this.spring.getContext().getBean(RestOperations.class); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.APPLICATION_JSON); - ResponseEntity> entity = new ResponseEntity<>(JSONObjectUtils.parse(response), headers, - HttpStatus.OK); - given(rest.exchange(any(RequestEntity.class), eq(new ParameterizedTypeReference>() { - }))).willReturn(entity); - } - catch (Exception ex) { - throw new IllegalArgumentException(ex); - } - } - - private String json(String name) throws IOException { - return resource(name + ".json"); - } - - private String jwks(String name) throws IOException { - return resource(name + ".jwks"); - } - - private String token(String name) throws IOException { - return resource(name + ".token"); - } - - private String resource(String suffix) throws IOException { - String name = this.getClass().getSimpleName() + "-" + suffix; - ClassPathResource resource = new ClassPathResource(name, this.getClass()); - try (BufferedReader reader = new BufferedReader(new FileReader(resource.getFile()))) { - return reader.lines().collect(Collectors.joining()); - } - } - - private T bean(Class beanClass) { - return this.spring.getContext().getBean(beanClass); - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - static class JwtDecoderFactoryBean implements FactoryBean { - - private RestOperations rest; - - private RSAPublicKey key; - - private OAuth2TokenValidator jwtValidator; - - @Override - public JwtDecoder getObject() { - NimbusJwtDecoder decoder; - if (this.key != null) { - decoder = NimbusJwtDecoder.withPublicKey(this.key).build(); - } - else { - decoder = NimbusJwtDecoder.withJwkSetUri("https://idp.example.org").restOperations(this.rest).build(); - } - if (this.jwtValidator != null) { - decoder.setJwtValidator(this.jwtValidator); - } - return decoder; - } - - @Override - public Class getObjectType() { - return JwtDecoder.class; - } - - public void setJwtValidator(OAuth2TokenValidator jwtValidator) { - this.jwtValidator = jwtValidator; - } - - public void setKey(RSAPublicKey key) { - this.key = key; - } - - public void setRest(RestOperations rest) { - this.rest = rest; - } - - } - - static class OpaqueTokenIntrospectorFactoryBean implements FactoryBean { - - private RestOperations rest; - - @Override - public OpaqueTokenIntrospector getObject() throws Exception { - return new SpringOpaqueTokenIntrospector("https://idp.example.org", this.rest); - } - - @Override - public Class getObjectType() { - return OpaqueTokenIntrospector.class; - } - - public void setRest(RestOperations rest) { - this.rest = rest; - } - - } - - static class MockWebServerFactoryBean implements FactoryBean, DisposableBean { - - private final MockWebServer web = new MockWebServer(); - - @Override - public void destroy() throws Exception { - this.web.shutdown(); - } - - @Override - public MockWebServer getObject() { - return this.web; - } - - @Override - public Class getObjectType() { - return MockWebServer.class; - } - - } - - static class MockWebServerPropertiesFactoryBean implements FactoryBean, DisposableBean { - - MockWebServer web; - - MockWebServerPropertiesFactoryBean(MockWebServer web) { - this.web = web; - } - - @Override - public Properties getObject() { - Properties p = new Properties(); - p.setProperty("jwk-set-uri", this.web.url("").toString()); - p.setProperty("introspection-uri", this.web.url("").toString()); - p.setProperty("issuer-one", this.web.url("issuerOne").toString()); - p.setProperty("issuer-two", this.web.url("issuerTwo").toString()); - return p; - } - - @Override - public Class getObjectType() { - return Properties.class; - } - - @Override - public void destroy() throws Exception { - this.web.shutdown(); - } - - } - - static class ClockFactoryBean implements FactoryBean { - - Clock clock; - - @Override - public Clock getObject() { - return this.clock; - } - - @Override - public Class getObjectType() { - return Clock.class; - } - - public void setMillis(long millis) { - this.clock = Clock.fixed(Instant.ofEpochMilli(millis), ZoneId.systemDefault()); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/PlaceHolderAndELConfigTests.java b/config/src/test/java/org/springframework/security/config/http/PlaceHolderAndELConfigTests.java deleted file mode 100644 index 72597f35088..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/PlaceHolderAndELConfigTests.java +++ /dev/null @@ -1,181 +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.http; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Josh Cummings - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class PlaceHolderAndELConfigTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/PlaceHolderAndELConfigTests"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void getWhenUsingPlaceholderThenUnsecuredPatternCorrectlyConfigured() throws Exception { - System.setProperty("pattern.nofilters", "/unsecured"); - this.spring.configLocations(this.xml("UnsecuredPattern")).autowire(); - // @formatter:off - this.mvc.perform(get("/unsecured")) - .andExpect(status().isOk()); - // @formatter:on - } - - /** - * SEC-1201 - */ - @Test - public void loginWhenUsingPlaceholderThenInterceptUrlsAndFormLoginWorks() throws Exception { - System.setProperty("secure.Url", "/secured"); - System.setProperty("secure.role", "ROLE_NUNYA"); - System.setProperty("login.page", "/loginPage"); - System.setProperty("default.target", "/defaultTarget"); - System.setProperty("auth.failure", "/authFailure"); - this.spring.configLocations(this.xml("InterceptUrlAndFormLogin")).autowire(); - // login-page setting - // @formatter:off - this.mvc.perform(get("/secured")) - .andExpect(redirectedUrl("/loginPage")); - // login-processing-url setting - // default-target-url setting - this.mvc.perform(post("/loginPage").param("username", "user").param("password", "password")) - .andExpect(redirectedUrl("/defaultTarget")); - // authentication-failure-url setting - this.mvc.perform(post("/loginPage").param("username", "user").param("password", "wrong")) - .andExpect(redirectedUrl("/authFailure")); - // @formatter:on - } - - /** - * SEC-1309 - */ - @Test - public void loginWhenUsingSpELThenInterceptUrlsAndFormLoginWorks() throws Exception { - System.setProperty("secure.url", "/secured"); - System.setProperty("secure.role", "ROLE_NUNYA"); - System.setProperty("login.page", "/loginPage"); - System.setProperty("default.target", "/defaultTarget"); - System.setProperty("auth.failure", "/authFailure"); - this.spring.configLocations(this.xml("InterceptUrlAndFormLoginWithSpEL")).autowire(); - // login-page setting - // @formatter:off - this.mvc.perform(get("/secured")) - .andExpect(redirectedUrl("/loginPage")); - // login-processing-url setting - // default-target-url setting - this.mvc.perform(post("/loginPage").param("username", "user").param("password", "password")) - .andExpect(redirectedUrl("/defaultTarget")); - // authentication-failure-url setting - this.mvc.perform(post("/loginPage").param("username", "user").param("password", "wrong")) - .andExpect(redirectedUrl("/authFailure")); - // @formatter:on - } - - @Test - @WithMockUser - public void requestWhenUsingPlaceholderOrSpELThenPortMapperWorks() throws Exception { - System.setProperty("http", "9080"); - System.setProperty("https", "9443"); - this.spring.configLocations(this.xml("PortMapping")).autowire(); - // @formatter:off - this.mvc.perform(get("http://localhost:9080/secured")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("https://localhost:9443/secured")); - this.mvc.perform(get("https://localhost:9443/unsecured")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("http://localhost:9080/unsecured")); - // @formatter:on - } - - @Test - @WithMockUser - public void requestWhenUsingPlaceholderThenRequiresChannelWorks() throws Exception { - System.setProperty("secure.url", "/secured"); - System.setProperty("required.channel", "https"); - this.spring.configLocations(this.xml("RequiresChannel")).autowire(); - // @formatter:off - this.mvc.perform(get("http://localhost/secured")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("https://localhost/secured")); - // @formatter:on - } - - @Test - @WithMockUser - public void requestWhenUsingPlaceholderThenAccessDeniedPageWorks() throws Exception { - System.setProperty("accessDenied", "/go-away"); - this.spring.configLocations(this.xml("AccessDeniedPage")).autowire(); - // @formatter:off - this.mvc.perform(get("/secured")) - .andExpect(forwardedUrl("/go-away")); - // @formatter:on - } - - @Test - @WithMockUser - public void requestWhenUsingSpELThenAccessDeniedPageWorks() throws Exception { - this.spring.configLocations(this.xml("AccessDeniedPageWithSpEL")).autowire(); - // @formatter:off - this.mvc.perform(get("/secured")) - .andExpect(forwardedUrl("/go-away")); - // @formatter:on - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - @RestController - static class SimpleController { - - @GetMapping("/unsecured") - String unsecured() { - return "unsecured"; - } - - @GetMapping("/secured") - String secured() { - return "secured"; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/RememberMeConfigTests.java b/config/src/test/java/org/springframework/security/config/http/RememberMeConfigTests.java deleted file mode 100644 index e031add2329..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/RememberMeConfigTests.java +++ /dev/null @@ -1,356 +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.http; - -import java.util.Collections; - -import jakarta.servlet.http.Cookie; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.FatalBeanException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.security.TestDataSource; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices; -import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.ResultActions; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.verify; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Luke Taylor - * @author Rob Winch - * @author Oliver Becker - */ -@ExtendWith(SpringTestContextExtension.class) -public class RememberMeConfigTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/RememberMeConfigTests"; - - @Autowired - MockMvc mvc; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - public void requestWithRememberMeWhenUsingCustomTokenRepositoryThenAutomaticallyReauthenticates() throws Exception { - this.spring.configLocations(xml("WithTokenRepository")).autowire(); - // @formatter:off - MvcResult result = rememberAuthentication("user", "password") - .andExpect(cookie().secure(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, false)) - .andReturn(); - // @formatter:on - Cookie cookie = rememberMeCookie(result); - // @formatter:off - this.mvc.perform(get("/authenticated").cookie(cookie)) - .andExpect(status().isOk()); - // @formatter:on - JdbcTemplate template = this.spring.getContext().getBean(JdbcTemplate.class); - int count = template.queryForObject("select count(*) from persistent_logins", int.class); - assertThat(count).isEqualTo(1); - } - - @Test - public void requestWithRememberMeWhenUsingCustomDataSourceThenAutomaticallyReauthenticates() throws Exception { - this.spring.configLocations(xml("WithDataSource")).autowire(); - TestDataSource dataSource = this.spring.getContext().getBean(TestDataSource.class); - JdbcTemplate template = new JdbcTemplate(dataSource); - template.execute(JdbcTokenRepositoryImpl.CREATE_TABLE_SQL); - // @formatter:off - MvcResult result = rememberAuthentication("user", "password") - .andExpect(cookie().secure(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, false)) - .andReturn(); - // @formatter:on - Cookie cookie = rememberMeCookie(result); - // @formatter:off - this.mvc.perform(get("/authenticated").cookie(cookie)) - .andExpect(status().isOk()); - // @formatter:on - int count = template.queryForObject("select count(*) from persistent_logins", int.class); - assertThat(count).isEqualTo(1); - } - - @Test - public void requestWithRememberMeWhenUsingAuthenticationSuccessHandlerThenInvokesHandler() throws Exception { - this.spring.configLocations(xml("WithAuthenticationSuccessHandler")).autowire(); - TestDataSource dataSource = this.spring.getContext().getBean(TestDataSource.class); - JdbcTemplate template = new JdbcTemplate(dataSource); - template.execute(JdbcTokenRepositoryImpl.CREATE_TABLE_SQL); - // @formatter:off - MvcResult result = rememberAuthentication("user", "password") - .andExpect(cookie().secure(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, false)) - .andReturn(); - // @formatter:on - Cookie cookie = rememberMeCookie(result); - // @formatter:off - this.mvc.perform(get("/authenticated").cookie(cookie)) - .andExpect(redirectedUrl("/target")); - // @formatter:on - int count = template.queryForObject("select count(*) from persistent_logins", int.class); - assertThat(count).isEqualTo(1); - } - - @Test - public void requestWithRememberMeWhenUsingCustomRememberMeServicesThenAuthenticates() throws Exception { - // SEC-1281 - using key with external services - this.spring.configLocations(xml("WithServicesRef")).autowire(); - // @formatter:off - MvcResult result = rememberAuthentication("user", "password") - .andExpect(cookie().secure(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, false)) - .andExpect(cookie().maxAge(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, 5000)) - .andReturn(); - // @formatter:on - Cookie cookie = rememberMeCookie(result); - // @formatter:off - this.mvc.perform(get("/authenticated").cookie(cookie)) - .andExpect(status().isOk()); - // SEC-909 - this.mvc.perform(post("/logout").cookie(cookie).with(csrf())) - .andExpect(cookie().maxAge(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, 0)) - .andReturn(); - // @formatter:on - } - - @Test - public void logoutWhenUsingRememberMeDefaultsThenCookieIsCancelled() throws Exception { - this.spring.configLocations(xml("DefaultConfig")).autowire(); - MvcResult result = rememberAuthentication("user", "password").andReturn(); - Cookie cookie = rememberMeCookie(result); - // @formatter:off - this.mvc.perform(post("/logout").cookie(cookie).with(csrf())) - .andExpect(cookie().maxAge(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, 0)); - // @formatter:on - } - - @Test - public void requestWithRememberMeWhenTokenValidityIsConfiguredThenCookieReflectsCorrectExpiration() - throws Exception { - this.spring.configLocations(xml("TokenValidity")).autowire(); - // @formatter:off - MvcResult result = rememberAuthentication("user", "password") - .andExpect(cookie().maxAge(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, 10000)) - .andReturn(); - // @formatter:on - Cookie cookie = rememberMeCookie(result); - // @formatter:off - this.mvc.perform(get("/authenticated").cookie(cookie)) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void requestWithRememberMeWhenTokenValidityIsNegativeThenCookieReflectsCorrectExpiration() throws Exception { - this.spring.configLocations(xml("NegativeTokenValidity")).autowire(); - // @formatter:off - rememberAuthentication("user", "password") - .andExpect(cookie().maxAge(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, -1)); - // @formatter:on - } - - @Test - public void configureWhenUsingDataSourceAndANegativeTokenValidityThenThrowsWiringException() { - assertThatExceptionOfType(FatalBeanException.class) - .isThrownBy(() -> this.spring.configLocations(xml("NegativeTokenValidityWithDataSource")).autowire()); - } - - @Test - public void requestWithRememberMeWhenTokenValidityIsResolvedByPropertyPlaceholderThenCookieReflectsCorrectExpiration() - throws Exception { - this.spring.configLocations(xml("Sec2165")).autowire(); - rememberAuthentication("user", "password") - .andExpect(cookie().maxAge(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, 30)); - } - - @Test - public void requestWithRememberMeWhenUseSecureCookieIsTrueThenCookieIsSecure() throws Exception { - this.spring.configLocations(xml("SecureCookie")).autowire(); - rememberAuthentication("user", "password") - .andExpect(cookie().secure(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, true)); - } - - /** - * SEC-1827 - */ - @Test - public void requestWithRememberMeWhenUseSecureCookieIsFalseThenCookieIsNotSecure() throws Exception { - this.spring.configLocations(xml("Sec1827")).autowire(); - rememberAuthentication("user", "password") - .andExpect(cookie().secure(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY, false)); - } - - @Test - public void configureWhenUsingPersistentTokenRepositoryAndANegativeTokenValidityThenThrowsWiringException() { - assertThatExceptionOfType(BeanDefinitionParsingException.class).isThrownBy( - () -> this.spring.configLocations(xml("NegativeTokenValidityWithPersistentRepository")).autowire()); - } - - @Test - public void rememberMeWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { - this.spring.configLocations(xml("WithSecurityContextHolderStrategy")).autowire(); - MvcResult result = rememberAuthentication("user", "password").andReturn(); - Cookie cookie = rememberMeCookie(result); - // @formatter:off - this.mvc.perform(get("/authenticated").cookie(cookie)) - .andExpect(status().isOk()); - // @formatter:on - verify(this.spring.getContext().getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); - } - - @Test - public void requestWithRememberMeWhenUsingCustomUserDetailsServiceThenInvokesThisUserDetailsService() - throws Exception { - this.spring.configLocations(xml("WithUserDetailsService")).autowire(); - UserDetailsService userDetailsService = this.spring.getContext().getBean(UserDetailsService.class); - given(userDetailsService.loadUserByUsername("user")) - .willAnswer((invocation) -> new User("user", "{noop}password", Collections.emptyList())); - MvcResult result = rememberAuthentication("user", "password").andReturn(); - Cookie cookie = rememberMeCookie(result); - // @formatter:off - this.mvc.perform(get("/authenticated").cookie(cookie)) - .andExpect(status().isOk()); - // @formatter:on - verify(userDetailsService, atLeastOnce()).loadUserByUsername("user"); - } - - /** - * SEC-742 - */ - @Test - public void requestWithRememberMeWhenExcludingBasicAuthenticationFilterThenStillReauthenticates() throws Exception { - this.spring.configLocations(xml("Sec742")).autowire(); - // @formatter:off - MvcResult result = this.mvc.perform(login("user", "password").param("remember-me", "true").with(csrf())) - .andExpect(redirectedUrl("/messageList.html")) - .andReturn(); - // @formatter:on - Cookie cookie = rememberMeCookie(result); - // @formatter:off - this.mvc.perform(get("/authenticated").cookie(cookie)) - .andExpect(status().isOk()); - // @formatter:on - } - - /** - * SEC-2119 - */ - @Test - public void requestWithRememberMeWhenUsingCustomRememberMeParameterThenReauthenticates() throws Exception { - this.spring.configLocations(xml("WithRememberMeParameter")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = login("user", "password") - .param("custom-remember-me-parameter", "true") - .with(csrf()); - MvcResult result = this.mvc.perform(request) - .andExpect(redirectedUrl("/")) - .andReturn(); - // @formatter:on - Cookie cookie = rememberMeCookie(result); - // @formatter:off - this.mvc.perform(get("/authenticated").cookie(cookie)) - .andExpect(status().isOk()); - // @formatter:on - } - - @Test - public void configureWhenUsingRememberMeParameterAndServicesRefThenThrowsWiringException() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(xml("WithRememberMeParameterAndServicesRef")).autowire()); - } - - /** - * SEC-2826 - */ - @Test - public void authenticateWhenUsingCustomRememberMeCookieNameThenIssuesCookieWithThatName() throws Exception { - this.spring.configLocations(xml("WithRememberMeCookie")).autowire(); - // @formatter:off - rememberAuthentication("user", "password") - .andExpect(cookie().exists("custom-remember-me-cookie")); - // @formatter:on - } - - /** - * SEC-2826 - */ - @Test - public void configureWhenUsingRememberMeCookieAndServicesRefThenThrowsWiringException() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(xml("WithRememberMeCookieAndServicesRef")).autowire()) - .withMessageContaining("Configuration problem: services-ref can't be used in combination with attributes " - + "token-repository-ref,data-source-ref, user-service-ref, token-validity-seconds, " - + "use-secure-cookie, remember-me-parameter or remember-me-cookie"); - } - - private ResultActions rememberAuthentication(String username, String password) throws Exception { - // @formatter:off - MockHttpServletRequestBuilder request = login(username, password) - .param(AbstractRememberMeServices.DEFAULT_PARAMETER, "true") - .with(csrf()); - return this.mvc.perform(request) - .andExpect(redirectedUrl("/")); - // @formatter:on - } - - private static MockHttpServletRequestBuilder login(String username, String password) { - // @formatter:off - return post("/login") - .param("username", username) - .param("password", password); - // @formatter:on - } - - private static Cookie rememberMeCookie(MvcResult result) { - return result.getResponse().getCookie("remember-me"); - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - @RestController - static class BasicController { - - @GetMapping("/authenticated") - String ok() { - return "ok"; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java deleted file mode 100644 index cf0dc447d0e..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests.java +++ /dev/null @@ -1,358 +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.http; - -import java.nio.charset.StandardCharsets; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import net.shibboleth.shared.xml.SerializeSupport; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; -import org.opensaml.core.xml.io.Marshaller; -import org.opensaml.saml.saml2.core.Assertion; -import org.opensaml.saml.saml2.core.Response; -import org.w3c.dom.Element; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationListener; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.event.AuthenticationSuccessEvent; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.saml2.core.OpenSamlInitializationService; -import org.springframework.security.saml2.core.Saml2ParameterNames; -import org.springframework.security.saml2.core.Saml2Utils; -import org.springframework.security.saml2.core.TestSaml2X509Credentials; -import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; -import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken; -import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest; -import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; -import org.springframework.security.saml2.provider.service.web.Saml2AuthenticationRequestRepository; -import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver; -import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.web.authentication.AuthenticationConverter; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.savedrequest.RequestCache; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.test.web.servlet.result.MockMvcResultHandlers; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link Saml2LoginBeanDefinitionParser} - * - * @author Marcus da Coregio - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class Saml2LoginBeanDefinitionParserTests { - - static { - OpenSamlInitializationService.initialize(); - } - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests"; - - private static final RelyingPartyRegistration registration = TestRelyingPartyRegistrations.noCredentials() - .signingX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartySigningCredential())) - .assertingPartyMetadata((party) -> party - .verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential()))) - .build(); - - private static String SIGNED_RESPONSE; - - private static final String IDP_SSO_URL = "https://sso-url.example.com/IDP/SSO"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired(required = false) - private RequestCache requestCache; - - @Autowired(required = false) - private AuthenticationFailureHandler authenticationFailureHandler; - - @Autowired(required = false) - private AuthenticationSuccessHandler authenticationSuccessHandler; - - @Autowired(required = false) - private RelyingPartyRegistrationRepository repository; - - @Autowired(required = false) - private ApplicationListener authenticationSuccessListener; - - @Autowired(required = false) - private AuthenticationConverter authenticationConverter; - - @Autowired(required = false) - private Saml2AuthenticationRequestResolver authenticationRequestResolver; - - @Autowired(required = false) - private Saml2AuthenticationRequestRepository authenticationRequestRepository; - - @Autowired(required = false) - private ApplicationContext applicationContext; - - @Autowired - private MockMvc mvc; - - @BeforeAll - static void createResponse() throws Exception { - String destination = registration.getAssertionConsumerServiceLocation(); - String assertingPartyEntityId = registration.getAssertingPartyMetadata().getEntityId(); - String relyingPartyEntityId = registration.getEntityId(); - Response response = TestOpenSamlObjects.response(destination, assertingPartyEntityId); - Assertion assertion = TestOpenSamlObjects.assertion("test@saml.user", assertingPartyEntityId, - relyingPartyEntityId, destination); - response.getAssertions().add(assertion); - Response signed = TestOpenSamlObjects.signed(response, - registration.getSigningX509Credentials().iterator().next(), relyingPartyEntityId); - Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(signed); - Element element = marshaller.marshall(signed); - String serialized = SerializeSupport.nodeToString(element); - SIGNED_RESPONSE = Saml2Utils.samlEncode(serialized.getBytes(StandardCharsets.UTF_8)); - } - - @Test - public void requestWhenSingleRelyingPartyRegistrationThenAutoRedirect() throws Exception { - this.spring.configLocations(this.xml("SingleRelyingPartyRegistration")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/saml2/authenticate/one")); - // @formatter:on - verify(this.requestCache).saveRequest(any(), any()); - } - - @Test - public void requestWhenMultiRelyingPartyRegistrationThenRedirectToLoginWithRelyingParties() throws Exception { - this.spring.configLocations(this.xml("MultiRelyingPartyRegistration")).autowire(); - // @formatter:off - this.mvc.perform(get("/")) - .andExpect(status().is3xxRedirection()) - .andExpect(redirectedUrl("/login")); - // @formatter:on - } - - @Test - public void requestLoginWhenMultiRelyingPartyRegistrationThenReturnLoginPageWithRelyingParties() throws Exception { - this.spring.configLocations(this.xml("MultiRelyingPartyRegistration")).autowire(); - // @formatter:off - MvcResult mvcResult = this.mvc.perform(get("/login")) - .andExpect(status().is2xxSuccessful()) - .andReturn(); - // @formatter:on - String pageContent = mvcResult.getResponse().getContentAsString(); - assertThat(pageContent).contains("two"); - assertThat(pageContent).contains("one"); - } - - @Test - public void authenticateWhenAuthenticationResponseNotValidThenThrowAuthenticationException() throws Exception { - this.spring.configLocations(this.xml("SingleRelyingPartyRegistration-WithCustomAuthenticationFailureHandler")) - .autowire(); - this.mvc.perform(get("/login/saml2/sso/one").param(Saml2ParameterNames.SAML_RESPONSE, "samlResponse123")); - ArgumentCaptor exceptionCaptor = ArgumentCaptor - .forClass(AuthenticationException.class); - verify(this.authenticationFailureHandler).onAuthenticationFailure(any(), any(), exceptionCaptor.capture()); - AuthenticationException exception = exceptionCaptor.getValue(); - assertThat(exception).isInstanceOf(Saml2AuthenticationException.class); - assertThat(((Saml2AuthenticationException) exception).getSaml2Error().getErrorCode()) - .isEqualTo("invalid_response"); - } - - @Test - public void authenticateWhenAuthenticationResponseValidThenAuthenticate() throws Exception { - this.spring.configLocations(this.xml("WithCustomRelyingPartyRepository")).autowire(); - RelyingPartyRegistration relyingPartyRegistration = relyingPartyRegistrationWithVerifyingCredential(); - // @formatter:off - this.mvc.perform(post("/login/saml2/sso/" + relyingPartyRegistration.getRegistrationId()).param(Saml2ParameterNames.SAML_RESPONSE, SIGNED_RESPONSE)) - .andDo(MockMvcResultHandlers.print()) - .andExpect(status().is2xxSuccessful()); - // @formatter:on - ArgumentCaptor authenticationCaptor = ArgumentCaptor.forClass(Authentication.class); - verify(this.authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), authenticationCaptor.capture()); - Authentication authentication = authenticationCaptor.getValue(); - assertThat(authentication.getPrincipal()).isInstanceOf(Saml2AuthenticatedPrincipal.class); - } - - @Test - public void authenticateWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { - this.spring.configLocations(this.xml("WithCustomSecurityContextHolderStrategy")).autowire(); - RelyingPartyRegistration relyingPartyRegistration = relyingPartyRegistrationWithVerifyingCredential(); - // @formatter:off - this.mvc.perform(post("/login/saml2/sso/" + relyingPartyRegistration.getRegistrationId()).param(Saml2ParameterNames.SAML_RESPONSE, SIGNED_RESPONSE)) - .andDo(MockMvcResultHandlers.print()) - .andExpect(status().is2xxSuccessful()); - // @formatter:on - ArgumentCaptor authenticationCaptor = ArgumentCaptor.forClass(Authentication.class); - verify(this.authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), authenticationCaptor.capture()); - Authentication authentication = authenticationCaptor.getValue(); - assertThat(authentication.getPrincipal()).isInstanceOf(Saml2AuthenticatedPrincipal.class); - SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); - verify(strategy, atLeastOnce()).getContext(); - } - - @Test - public void authenticateWhenAuthenticationResponseValidThenAuthenticationSuccessEventPublished() throws Exception { - this.spring.configLocations(this.xml("WithCustomRelyingPartyRepository")).autowire(); - RelyingPartyRegistration relyingPartyRegistration = relyingPartyRegistrationWithVerifyingCredential(); - // @formatter:off - this.mvc.perform(post("/login/saml2/sso/" + relyingPartyRegistration.getRegistrationId()).param(Saml2ParameterNames.SAML_RESPONSE, SIGNED_RESPONSE)) - .andDo(MockMvcResultHandlers.print()) - .andExpect(status().is2xxSuccessful()); - // @formatter:on - verify(this.authenticationSuccessListener).onApplicationEvent(any(AuthenticationSuccessEvent.class)); - } - - @Test - public void authenticateWhenCustomAuthenticationConverterThenUses() throws Exception { - this.spring.configLocations(this.xml("WithCustomRelyingPartyRepository-WithCustomAuthenticationConverter")) - .autowire(); - RelyingPartyRegistration relyingPartyRegistration = relyingPartyRegistrationWithVerifyingCredential(); - String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE)); - given(this.authenticationConverter.convert(any(HttpServletRequest.class))) - .willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response)); - // @formatter:off - MockHttpServletRequestBuilder request = post("/login/saml2/sso/" + relyingPartyRegistration.getRegistrationId()) - .param("SAMLResponse", SIGNED_RESPONSE); - // @formatter:on - this.mvc.perform(request).andExpect(status().is3xxRedirection()).andExpect(redirectedUrl("/")); - verify(this.authenticationConverter).convert(any(HttpServletRequest.class)); - } - - @Test - public void authenticateWhenCustomAuthenticationManagerThenUses() throws Exception { - this.spring.configLocations(this.xml("WithCustomRelyingPartyRepository-WithCustomAuthenticationManager")) - .autowire(); - RelyingPartyRegistration relyingPartyRegistration = relyingPartyRegistrationWithVerifyingCredential(); - AuthenticationManager authenticationManager = this.applicationContext.getBean("customAuthenticationManager", - AuthenticationManager.class); - String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE)); - given(authenticationManager.authenticate(any())) - .willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response)); - // @formatter:off - MockHttpServletRequestBuilder request = post("/login/saml2/sso/" + relyingPartyRegistration.getRegistrationId()) - .param("SAMLResponse", SIGNED_RESPONSE); - // @formatter:on - this.mvc.perform(request).andExpect(status().is3xxRedirection()).andExpect(redirectedUrl("/")); - verify(authenticationManager).authenticate(any()); - } - - @Test - public void authenticationRequestWhenCustomAuthenticationRequestContextResolverThenUses() throws Exception { - this.spring - .configLocations(this.xml("WithCustomRelyingPartyRepository-WithCustomAuthenticationRequestResolver")) - .autowire(); - Saml2RedirectAuthenticationRequest request = Saml2RedirectAuthenticationRequest - .withRelyingPartyRegistration(TestRelyingPartyRegistrations.noCredentials().build()) - .samlRequest("request") - .authenticationRequestUri(IDP_SSO_URL) - .build(); - given(this.authenticationRequestResolver.resolve(any(HttpServletRequest.class))).willReturn(request); - this.mvc.perform(get("/saml2/authenticate/registration-id")).andExpect(status().isFound()); - verify(this.authenticationRequestResolver).resolve(any(HttpServletRequest.class)); - } - - @Test - public void authenticationRequestWhenCustomAuthnRequestRepositoryThenUses() throws Exception { - this.spring.configLocations(this.xml("WithCustomRelyingPartyRepository-WithCustomAuthnRequestRepository")) - .autowire(); - given(this.repository.findByRegistrationId(anyString())) - .willReturn(TestRelyingPartyRegistrations.relyingPartyRegistration().build()); - MockHttpServletRequestBuilder request = get("/saml2/authenticate/registration-id"); - this.mvc.perform(request).andExpect(status().isFound()); - verify(this.authenticationRequestRepository).saveAuthenticationRequest( - any(AbstractSaml2AuthenticationRequest.class), any(HttpServletRequest.class), - any(HttpServletResponse.class)); - } - - @Test - public void authenticateWhenCustomAuthnRequestRepositoryThenUses() throws Exception { - this.spring.configLocations(this.xml("WithCustomRelyingPartyRepository-WithCustomAuthnRequestRepository")) - .autowire(); - RelyingPartyRegistrationRepository repository = mock(RelyingPartyRegistrationRepository.class); - given(this.repository.findByRegistrationId(anyString())) - .willReturn(TestRelyingPartyRegistrations.relyingPartyRegistration().build()); - MockHttpServletRequestBuilder request = post("/login/saml2/sso/registration-id").param("SAMLResponse", - SIGNED_RESPONSE); - this.mvc.perform(request); - verify(this.authenticationRequestRepository).loadAuthenticationRequest(any(HttpServletRequest.class)); - verify(this.authenticationRequestRepository).removeAuthenticationRequest(any(HttpServletRequest.class), - any(HttpServletResponse.class)); - } - - @Test - public void saml2LoginWhenLoginProcessingUrlWithoutRegistrationIdAndDefaultAuthenticationConverterThenValidates() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> this.spring.configLocations(this.xml("WithCustomLoginProcessingUrl")).autowire()) - .withMessageContaining("loginProcessingUrl must contain {registrationId} path variable"); - } - - @Test - public void authenticateWhenCustomLoginProcessingUrlAndCustomAuthenticationConverterThenAuthenticate() - throws Exception { - this.spring.configLocations(this.xml("WithCustomLoginProcessingUrl-WithCustomAuthenticationConverter")) - .autowire(); - String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE)); - given(this.authenticationConverter.convert(any(HttpServletRequest.class))) - .willReturn(new Saml2AuthenticationToken(registration, response)); - // @formatter:off - MockHttpServletRequestBuilder request = post("/my/custom/url").param("SAMLResponse", SIGNED_RESPONSE); - // @formatter:on - this.mvc.perform(request).andExpect(redirectedUrl("/")); - verify(this.authenticationConverter).convert(any(HttpServletRequest.class)); - } - - private RelyingPartyRegistration relyingPartyRegistrationWithVerifyingCredential() { - given(this.repository.findByRegistrationId(anyString())).willReturn(registration); - return registration; - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParserTests.java deleted file mode 100644 index 0fc302976d9..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/Saml2LogoutBeanDefinitionParserTests.java +++ /dev/null @@ -1,427 +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.http; - -import java.nio.charset.StandardCharsets; -import java.time.Instant; -import java.util.Collections; -import java.util.Map; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.opensaml.saml.saml2.core.LogoutRequest; -import org.opensaml.xmlsec.signature.support.SignatureConstants; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.saml2.core.Saml2Utils; -import org.springframework.security.saml2.provider.service.authentication.DefaultSaml2AuthenticatedPrincipal; -import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; -import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequestValidator; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponse; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutResponseValidator; -import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutValidatorResult; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository; -import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository; -import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver; -import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver; -import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.request.RequestPostProcessor; -import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UriUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.verify; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link Saml2LogoutBeanDefinitionParser} - * - * @author Marcus da Coregio - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class Saml2LogoutBeanDefinitionParserTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - private final Saml2LogoutRequestRepository logoutRequestRepository = new HttpSessionLogoutRequestRepository(); - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/Saml2LogoutBeanDefinitionParserTests"; - - String apLogoutRequest = "nZFBa4MwGIb/iuQeE2NTXFDLQAaC26Hrdtgt1dQFNMnyxdH9+zlboeyww275SN7nzcOX787jEH0qD9qaAiUxRZEyre206Qv0cnjAGdqVOchxYE40trdT2KuPSUGI5qQBcbkq0OSNsBI0CCNHBSK04vn+sREspsJ5G2xrBxRVc1AbGZa29xAcCEK8i9VZjm5QsfU9GZYWsoCJv5ShqK4K1Ow5p5LyU4aP6XaLN3cpw9mGctydjrxNaZt1XM5vASZVGwjShAIxyhJMU8z4gSWCM8GSmDH+hqLX1Xv+JLpaiiXsb+3+lpMAyv8IoVI6rEzQ4QvrLie3uBX+NMfr6l/waT6t0AumvI6/FlN+Aw=="; - - String apLogoutRequestSigAlg = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; - - String apLogoutRequestRelayState = "33591874-b123-4f2c-ab0d-2d0d84aa8b56"; - - String apLogoutRequestSignature = "oKqdzrmn2YAqXcwkow2lzRXr5PNHm0s/gWsRnaZYhC+Oq5ekK5uIKQYvtmNR94HJjDe1VRs+vVQCYivgdoTzBV2ZlffTXZmYsCsY9q4jbCWR6R5CbhU73/MkKQsPcyVvMhNYxnDYapIlxDsfoZNTboDEz3GM+HRoGRfl9emCXY0lPRYwqC4kpu7oMDBkafR0A09jPIxFuNpqlLPwUxL9m+DGkvDK3mFDN1xJcgZaK73HcuJe7Qh4huOrKNFetwc5EvqfiwgiWF6sfq9A+rZBfCIYo10NNLY7fNQAR2IqwcKtawHgTGWbeshRyFrwVYMR64EnClfxUHsHKf5kiZ2dlw=="; - - String apLogoutResponse = "fZHRa4MwEMb/Fcl7jEadGqplrAwK3Uvb9WFvZ4ydoInk4uj++1nXbmWMvhwcd9/3Jb9bLE99530oi63RBQn9gHhKS1O3+liQ1/0zzciyXCD0HR/ExhzN6LYKB6NReZNUo/ieFWS0WhjAFoWGXqFwUuweXzaC+4EYrHFGmo54K4Wu1eDmuHfnBhSM2cFXJ+iHTvnGHlk3x7DZmNlLGvHWq4Jstk0GUSjjiIZJI2lcpQnNeRLTAOo4fwCeQg3Trr6+cm/OqmnWVHECVGWQ0jgCSatsKvXUxhFvZF7xSYU4qrVGB9oVhAc8pEFEebLnkeBc8NyPePpGvMOV1/Q3cqEjZrG9hXKfCSAqe+ZAShio0q51n7StF+zW7gf9zoEb8U/7ZGrlHaAb1f0onLfFbpRSIRJWXkJ+bdm/Fy6/AA=="; - - String apLogoutResponseSigAlg = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256; - - String apLogoutResponseRelayState = "8f63887a-ec7e-4149-b6a0-dd730017f315"; - - String apLogoutResponseSignature = "h2fDqSIBfmnkRHKDMY4IxkCXcI0w98ydNsnPmv1b7GTZCWLbJ+oxaP2yZNPw7wOWXTv86cTPwKLjx5halKy5C+hhWnT0haKhuMcUvHlsgAMBbJKLV+1afzL4O77cvAQJmMNRK7ugXGNV5PTEnd1U4voy134OgdD5XycYiFVRZOwP5H84eJ9xxlvqQwqDvZTcgiF/ZS4ioZgzgnIFcbagZQ12LWNh26OMaUpIW04kCeO6t2dUsxOL6nZWvNrX/Zx1sORIpu4doDUa1RYC8YnjZeQEzDqUVC/dBO/mbVJ/hbF9tD0jBUx7YIgoXpqsWK4TcCsvmlmhrJXvGxDyoAWu2Q=="; - - String rpLogoutRequest = "nZFBa4MwGIb/iuQeY6NlGtQykIHgdui6HXaLmrqAJlm+OLp/v0wrlB122CXkI3mfNw/JD5dpDD6FBalVgXZhhAKhOt1LNRTo5fSAU3Qoc+DTSA1r9KBndxQfswAX+KQCth4VaLaKaQ4SmOKTAOY69nz/2DAaRsxY7XSnRxRUPigVd0vbu3MGGCHchOLCJzOKUNuBjEsLWcDErmUoqKsCNcc+yc5tsudYpPwOJzHvcJv6pfdjEtNzl7XU3wWYRa3AceUKRCO6w1GM6f5EY0Ypo1lIk+gNBa+bt38kulqyJWxv7f6W4wDC/gih0hoslJPuC8s+J7e4Df7k43X1L/jsdxt0xZTX8dfHlN8="; - - String rpLogoutRequestId = "LRd49fb45a-e8a7-43ac-b8ac-d8a7432fc9b2"; - - String rpLogoutRequestRelayState = "8f63887a-ec7e-4149-b6a0-dd730017f315"; - - String rpLogoutRequestSignature = "h2fDqSIBfmnkRHKDMY4IxkCXcI0w98ydNsnPmv1b7GTZCWLbJ+oxaP2yZNPw7wOWXTv86cTPwKLjx5halKy5C+hhWnT0haKhuMcUvHlsgAMBbJKLV+1afzL4O77cvAQJmMNRK7ugXGNV5PTEnd1U4voy134OgdD5XycYiFVRZOwP5H84eJ9xxlvqQwqDvZTcgiF/ZS4ioZgzgnIFcbagZQ12LWNh26OMaUpIW04kCeO6t2dUsxOL6nZWvNrX/Zx1sORIpu4doDUa1RYC8YnjZeQEzDqUVC/dBO/mbVJ/hbF9tD0jBUx7YIgoXpqsWK4TcCsvmlmhrJXvGxDyoAWu2Q=="; - - @Autowired(required = false) - private RelyingPartyRegistrationRepository repository; - - @Autowired - private MockMvc mvc; - - private Saml2Authentication saml2User; - - private MockHttpServletRequest request; - - private MockHttpServletResponse response; - - @BeforeEach - public void setup() { - DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", - Collections.emptyMap()); - principal.setRelyingPartyRegistrationId("registration-id"); - this.saml2User = new Saml2Authentication(principal, "response", - AuthorityUtils.createAuthorityList("ROLE_USER")); - this.request = new MockHttpServletRequest("POST", "/login/saml2/sso/test-rp"); - this.response = new MockHttpServletResponse(); - } - - @Test - public void logoutWhenLogoutSuccessHandlerAndNotSaml2LoginThenDefaultLogoutSuccessHandler() throws Exception { - this.spring.configLocations(this.xml("LogoutSuccessHandler")).autowire(); - TestingAuthenticationToken user = new TestingAuthenticationToken("user", "password"); - MvcResult result = this.mvc.perform(post("/logout").with(authentication(user)).with(csrf())) - .andExpect(status().isFound()) - .andReturn(); - String location = result.getResponse().getHeader("Location"); - assertThat(location).isEqualTo("/logoutSuccessEndpoint"); - } - - @Test - public void saml2LogoutWhenDefaultsThenLogsOutAndSendsLogoutRequest() throws Exception { - this.spring.configLocations(this.xml("Default")).autowire(); - MvcResult result = this.mvc.perform(post("/logout").with(authentication(this.saml2User)).with(csrf())) - .andExpect(status().isFound()) - .andReturn(); - String location = result.getResponse().getHeader("Location"); - assertThat(location).startsWith("https://ap.example.org/logout/saml2/request"); - } - - @Test - public void saml2LogoutWhenUnauthenticatedThenEntryPoint() throws Exception { - this.spring.configLocations(this.xml("Default")).autowire(); - this.mvc.perform(post("/logout").with(csrf())) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - } - - @Test - public void saml2LogoutWhenMissingCsrfThen403() throws Exception { - this.spring.configLocations(this.xml("Default")).autowire(); - this.mvc.perform(post("/logout").with(authentication(this.saml2User))).andExpect(status().isForbidden()); - } - - @Test - public void saml2LogoutWhenGetThenDefaultLogoutPage() throws Exception { - this.spring.configLocations(this.xml("Default")).autowire(); - MvcResult result = this.mvc.perform(get("/logout").with(authentication(this.saml2User))) - .andExpect(status().isOk()) - .andReturn(); - assertThat(result.getResponse().getContentAsString()).contains("Are you sure you want to log out?"); - } - - @Test - public void saml2LogoutWhenPutOrDeleteThen404() throws Exception { - this.spring.configLocations(this.xml("Default")).autowire(); - this.mvc.perform(put("/logout").with(authentication(this.saml2User)).with(csrf())) - .andExpect(status().isNotFound()); - this.mvc.perform(delete("/logout").with(authentication(this.saml2User)).with(csrf())) - .andExpect(status().isNotFound()); - } - - @Test - public void saml2LogoutWhenNoRegistrationThen401() throws Exception { - this.spring.configLocations(this.xml("Default")).autowire(); - DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", - Collections.emptyMap()); - principal.setRelyingPartyRegistrationId("wrong"); - Saml2Authentication authentication = new Saml2Authentication(principal, "response", - AuthorityUtils.createAuthorityList("ROLE_USER")); - this.mvc.perform(post("/logout").with(authentication(authentication)).with(csrf())) - .andExpect(status().isUnauthorized()); - } - - @Test - public void saml2LogoutWhenCsrfDisabledAndNoAuthenticationThenFinalRedirect() throws Exception { - this.spring.configLocations(this.xml("CsrfDisabled-MockLogoutSuccessHandler")).autowire(); - this.mvc.perform(post("/logout")); - LogoutSuccessHandler logoutSuccessHandler = this.spring.getContext().getBean(LogoutSuccessHandler.class); - verify(logoutSuccessHandler).onLogoutSuccess(any(), any(), any()); - } - - @Test - public void saml2LogoutWhenCustomLogoutRequestResolverThenUses() throws Exception { - this.spring.configLocations(this.xml("CustomComponents")).autowire(); - RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id"); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) - .samlRequest(this.rpLogoutRequest) - .id(this.rpLogoutRequestId) - .relayState(this.rpLogoutRequestRelayState) - .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) - .build(); - given(getBean(Saml2LogoutRequestResolver.class).resolve(any(), any())).willReturn(logoutRequest); - this.mvc.perform(post("/logout").with(authentication(this.saml2User)).with(csrf())); - verify(getBean(Saml2LogoutRequestResolver.class)).resolve(any(), any()); - } - - @Test - public void saml2LogoutRequestWhenDefaultsThenLogsOutAndSendsLogoutResponse() throws Exception { - this.spring.configLocations(this.xml("Default")).autowire(); - DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", - Collections.emptyMap()); - principal.setRelyingPartyRegistrationId("get"); - Saml2Authentication user = new Saml2Authentication(principal, "response", - AuthorityUtils.createAuthorityList("ROLE_USER")); - MvcResult result = this.mvc - .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) - .param("RelayState", this.apLogoutRequestRelayState) - .param("SigAlg", this.apLogoutRequestSigAlg) - .param("Signature", this.apLogoutRequestSignature) - .with(samlQueryString()) - .with(authentication(user))) - .andExpect(status().isFound()) - .andReturn(); - String location = result.getResponse().getHeader("Location"); - assertThat(location).startsWith("https://ap.example.org/logout/saml2/response"); - } - - @Test - public void saml2LogoutRequestWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { - this.spring.configLocations(this.xml("WithSecurityContextHolderStrategy")).autowire(); - DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", - Collections.emptyMap()); - principal.setRelyingPartyRegistrationId("get"); - Saml2Authentication user = new Saml2Authentication(principal, "response", - AuthorityUtils.createAuthorityList("ROLE_USER")); - MvcResult result = this.mvc - .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) - .param("RelayState", this.apLogoutRequestRelayState) - .param("SigAlg", this.apLogoutRequestSigAlg) - .param("Signature", this.apLogoutRequestSignature) - .with(samlQueryString()) - .with(authentication(user))) - .andExpect(status().isFound()) - .andReturn(); - String location = result.getResponse().getHeader("Location"); - assertThat(location).startsWith("https://ap.example.org/logout/saml2/response"); - verify(getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); - } - - @Test - public void saml2LogoutRequestWhenNoRegistrationThen400() throws Exception { - this.spring.configLocations(this.xml("Default")).autowire(); - DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", - Collections.emptyMap()); - principal.setRelyingPartyRegistrationId("wrong"); - Saml2Authentication user = new Saml2Authentication(principal, "response", - AuthorityUtils.createAuthorityList("ROLE_USER")); - this.mvc - .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) - .param("RelayState", this.apLogoutRequestRelayState) - .param("SigAlg", this.apLogoutRequestSigAlg) - .param("Signature", this.apLogoutRequestSignature) - .with(authentication(user))) - .andExpect(status().isBadRequest()); - } - - // gh-14635 - @Test - public void saml2LogoutRequestWhenInvalidSamlRequestThen302Redirect() throws Exception { - this.spring.configLocations(this.xml("Default")).autowire(); - this.mvc - .perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) - .param("RelayState", this.apLogoutRequestRelayState) - .param("SigAlg", this.apLogoutRequestSigAlg) - .with(authentication(this.saml2User))) - .andExpect(status().isFound()); - } - - @Test - public void saml2LogoutRequestWhenCustomLogoutRequestHandlerThenUses() throws Exception { - this.spring.configLocations(this.xml("CustomComponents")).autowire(); - RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id"); - LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration); - logoutRequest.setIssueInstant(Instant.now()); - given(getBean(Saml2LogoutRequestValidator.class).validate(any())) - .willReturn(Saml2LogoutValidatorResult.success()); - Saml2LogoutResponse logoutResponse = Saml2LogoutResponse.withRelyingPartyRegistration(registration).build(); - given(getBean(Saml2LogoutResponseResolver.class).resolve(any(), any())).willReturn(logoutResponse); - this.mvc - .perform(post("/logout/saml2/slo").param("SAMLRequest", "samlRequest").with(authentication(this.saml2User))) - .andReturn(); - verify(getBean(Saml2LogoutRequestValidator.class)).validate(any()); - verify(getBean(Saml2LogoutResponseResolver.class)).resolve(any(), any()); - } - - @Test - public void saml2LogoutResponseWhenDefaultsThenRedirects() throws Exception { - this.spring.configLocations(this.xml("Default")).autowire(); - RelyingPartyRegistration registration = this.repository.findByRegistrationId("get"); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) - .samlRequest(this.rpLogoutRequest) - .id(this.rpLogoutRequestId) - .relayState(this.rpLogoutRequestRelayState) - .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) - .build(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest, this.request, this.response); - this.request.setParameter("RelayState", logoutRequest.getRelayState()); - assertThat(this.logoutRequestRepository.loadLogoutRequest(this.request)).isNotNull(); - this.mvc - .perform(get("/logout/saml2/slo").session(((MockHttpSession) this.request.getSession())) - .param("SAMLResponse", this.apLogoutResponse) - .param("RelayState", this.apLogoutResponseRelayState) - .param("SigAlg", this.apLogoutResponseSigAlg) - .param("Signature", this.apLogoutResponseSignature) - .with(samlQueryString())) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login?logout")); - assertThat(this.logoutRequestRepository.loadLogoutRequest(this.request)).isNull(); - } - - @Test - public void saml2LogoutResponseWhenInvalidSamlResponseThen401() throws Exception { - this.spring.configLocations(this.xml("Default")).autowire(); - RelyingPartyRegistration registration = this.repository.findByRegistrationId("registration-id"); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) - .samlRequest(this.rpLogoutRequest) - .id(this.rpLogoutRequestId) - .relayState(this.rpLogoutRequestRelayState) - .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) - .build(); - this.logoutRequestRepository.saveLogoutRequest(logoutRequest, this.request, this.response); - String deflatedApLogoutResponse = Saml2Utils.samlEncode( - Saml2Utils.samlInflate(Saml2Utils.samlDecode(this.apLogoutResponse)).getBytes(StandardCharsets.UTF_8)); - this.mvc - .perform(post("/logout/saml2/slo").session((MockHttpSession) this.request.getSession()) - .param("SAMLResponse", deflatedApLogoutResponse) - .param("RelayState", this.rpLogoutRequestRelayState) - .param("SigAlg", this.apLogoutRequestSigAlg) - .param("Signature", this.apLogoutResponseSignature) - .with(samlQueryString())) - .andExpect(status().reason(containsString("invalid_signature"))) - .andExpect(status().isUnauthorized()); - } - - @Test - public void saml2LogoutResponseWhenCustomLogoutResponseHandlerThenUses() throws Exception { - this.spring.configLocations(this.xml("CustomComponents")).autowire(); - RelyingPartyRegistration registration = this.repository.findByRegistrationId("get"); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) - .samlRequest(this.rpLogoutRequest) - .id(this.rpLogoutRequestId) - .relayState(this.rpLogoutRequestRelayState) - .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) - .build(); - given(getBean(Saml2LogoutRequestRepository.class).removeLogoutRequest(any(), any())).willReturn(logoutRequest); - given(getBean(Saml2LogoutResponseValidator.class).validate(any())) - .willReturn(Saml2LogoutValidatorResult.success()); - this.mvc.perform(get("/logout/saml2/slo").param("SAMLResponse", "samlResponse")).andReturn(); - verify(getBean(Saml2LogoutResponseValidator.class)).validate(any()); - } - - // gh-11363 - @Test - public void saml2LogoutWhenCustomLogoutRequestRepositoryThenUses() throws Exception { - this.spring.configLocations(this.xml("CustomComponents")).autowire(); - RelyingPartyRegistration registration = this.repository.findByRegistrationId("get"); - Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration) - .samlRequest(this.rpLogoutRequest) - .id(this.rpLogoutRequestId) - .relayState(this.rpLogoutRequestRelayState) - .parameters((params) -> params.put("Signature", this.rpLogoutRequestSignature)) - .build(); - given(getBean(Saml2LogoutRequestResolver.class).resolve(any(), any())).willReturn(logoutRequest); - this.mvc.perform(post("/logout").with(authentication(this.saml2User)).with(csrf())); - verify(getBean(Saml2LogoutRequestRepository.class)).saveLogoutRequest(eq(logoutRequest), any(), any()); - } - - private T getBean(Class clazz) { - return this.spring.getContext().getBean(clazz); - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - private SamlQueryStringRequestPostProcessor samlQueryString() { - return new SamlQueryStringRequestPostProcessor(); - } - - static class SamlQueryStringRequestPostProcessor implements RequestPostProcessor { - - @Override - public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { - UriComponentsBuilder builder = UriComponentsBuilder.newInstance(); - for (Map.Entry entries : request.getParameterMap().entrySet()) { - builder.queryParam(entries.getKey(), - UriUtils.encode(entries.getValue()[0], StandardCharsets.ISO_8859_1)); - } - request.setQueryString(builder.build(true).toUriString().substring(1)); - return request; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.java b/config/src/test/java/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.java deleted file mode 100644 index ff571c54f89..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests.java +++ /dev/null @@ -1,268 +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.http; - -import java.io.IOException; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.apache.http.HttpHeaders; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.CoreMatchers.containsString; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * @author Rob Winch - * @author Josh Cummings - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class SecurityContextHolderAwareRequestConfigTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/SecurityContextHolderAwareRequestConfigTests"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - private MockMvc mvc; - - @Test - public void servletLoginWhenUsingDefaultConfigurationThenUsesSpringSecurity() throws Exception { - this.spring.configLocations(this.xml("Simple")).autowire(); - // @formatter:off - this.mvc.perform(get("/good-login")) - .andExpect(status().isOk()) - .andExpect(content().string("user")); - // @formatter:on - } - - @Test - public void servletAuthenticateWhenUsingDefaultConfigurationThenUsesSpringSecurity() throws Exception { - this.spring.configLocations(this.xml("Simple")).autowire(); - // @formatter:off - this.mvc.perform(get("/authenticate")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); - // @formatter:on - } - - @Test - public void servletLogoutWhenUsingDefaultConfigurationThenUsesSpringSecurity() throws Exception { - this.spring.configLocations(this.xml("Simple")).autowire(); - MvcResult result = this.mvc.perform(get("/good-login")).andReturn(); - MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); - assertThat(session).isNotNull(); - // @formatter:off - result = this.mvc.perform(get("/do-logout").session(session)) - .andExpect(status().isOk()) - .andExpect(content().string("")) - .andReturn(); - // @formatter:on - session = (MockHttpSession) result.getRequest().getSession(false); - assertThat(session).isNull(); - } - - @Test - public void servletAuthenticateWhenUsingHttpBasicThenUsesSpringSecurity() throws Exception { - this.spring.configLocations(this.xml("HttpBasic")).autowire(); - // @formatter:off - this.mvc.perform(get("/authenticate")) - .andExpect(status().isUnauthorized()) - .andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, containsString("discworld"))); - // @formatter:on - } - - @Test - public void servletAuthenticateWhenUsingFormLoginThenUsesSpringSecurity() throws Exception { - this.spring.configLocations(this.xml("FormLogin")).autowire(); - // @formatter:off - this.mvc.perform(get("/authenticate")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); - // @formatter:on - } - - @Test - public void servletLoginWhenUsingMultipleHttpConfigsThenUsesSpringSecurity() throws Exception { - this.spring.configLocations(this.xml("MultiHttp")).autowire(); - // @formatter:off - this.mvc.perform(get("/good-login")) - .andExpect(status().isOk()) - .andExpect(content().string("user")); - this.mvc.perform(get("/v2/good-login")) - .andExpect(status().isOk()) - .andExpect(content().string("user2")); - // @formatter:on - } - - @Test - public void servletAuthenticateWhenUsingMultipleHttpConfigsThenUsesSpringSecurity() throws Exception { - this.spring.configLocations(this.xml("MultiHttp")).autowire(); - // @formatter:off - this.mvc.perform(get("/authenticate")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login")); - this.mvc.perform(get("/v2/authenticate")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/login2")); - // @formatter:on - } - - @Test - public void servletLogoutWhenUsingMultipleHttpConfigsThenUsesSpringSecurity() throws Exception { - this.spring.configLocations(this.xml("MultiHttp")).autowire(); - MvcResult result = this.mvc.perform(get("/good-login")).andReturn(); - MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); - assertThat(session).isNotNull(); - // @formatter:off - result = this.mvc.perform(get("/do-logout").session(session)) - .andExpect(status().isOk()) - .andExpect(content().string("")) - .andReturn(); - // @formatter:on - session = (MockHttpSession) result.getRequest().getSession(false); - assertThat(session).isNotNull(); - // @formatter:off - result = this.mvc.perform(get("/v2/good-login")) - .andReturn(); - // @formatter:on - session = (MockHttpSession) result.getRequest().getSession(false); - assertThat(session).isNotNull(); - // @formatter:off - result = this.mvc.perform(get("/v2/do-logout").session(session)) - .andExpect(status().isOk()) - .andExpect(content().string("")) - .andReturn(); - // @formatter:on - session = (MockHttpSession) result.getRequest().getSession(false); - assertThat(session).isNull(); - } - - @Test - public void servletLogoutWhenUsingCustomLogoutThenUsesSpringSecurity() throws Exception { - this.spring.configLocations(this.xml("Logout")).autowire(); - this.mvc.perform(get("/authenticate")).andExpect(status().isFound()).andExpect(redirectedUrl("/signin")); - // @formatter:off - MvcResult result = this.mvc.perform(get("/good-login")) - .andReturn(); - // @formatter:on - MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); - assertThat(session).isNotNull(); - // @formatter:off - result = this.mvc.perform(get("/do-logout").session(session)) - .andExpect(status().isOk()) - .andExpect(content().string("")) - .andExpect(cookie().maxAge("JSESSIONID", 0)) - .andReturn(); - // @formatter:on - session = (MockHttpSession) result.getRequest().getSession(false); - assertThat(session).isNotNull(); - } - - /** - * SEC-2926: Role Prefix is set - */ - @Test - @WithMockUser - public void servletIsUserInRoleWhenUsingDefaultConfigThenRoleIsSet() throws Exception { - this.spring.configLocations(this.xml("Simple")).autowire(); - // @formatter:off - this.mvc.perform(get("/role")) - .andExpect(content().string("true")); - // @formatter:on - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - @RestController - public static class ServletAuthenticatedController { - - @GetMapping("/v2/good-login") - public String v2Login(HttpServletRequest request) throws ServletException { - request.login("user2", "password2"); - return this.principal(); - } - - @GetMapping("/good-login") - public String login(HttpServletRequest request) throws ServletException { - request.login("user", "password"); - return this.principal(); - } - - @GetMapping("/v2/authenticate") - public String v2Authenticate(HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException { - return this.authenticate(request, response); - } - - @GetMapping("/authenticate") - public String authenticate(HttpServletRequest request, HttpServletResponse response) - throws IOException, ServletException { - request.authenticate(response); - return this.principal(); - } - - @GetMapping("/v2/do-logout") - public String v2Logout(HttpServletRequest request) throws ServletException { - return this.logout(request); - } - - @GetMapping("/do-logout") - public String logout(HttpServletRequest request) throws ServletException { - request.logout(); - return this.principal(); - } - - @GetMapping("/role") - public String role(HttpServletRequest request) { - return String.valueOf(request.isUserInRole("USER")); - } - - private String principal() { - if (SecurityContextHolder.getContext().getAuthentication() != null) { - return SecurityContextHolder.getContext().getAuthentication().getName(); - } - return null; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/SecurityFiltersAssertions.java b/config/src/test/java/org/springframework/security/config/http/SecurityFiltersAssertions.java deleted file mode 100644 index 41cfec4ba9d..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/SecurityFiltersAssertions.java +++ /dev/null @@ -1,44 +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.http; - -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.stream.Collectors; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Assertions for tests that rely on confirming behavior of the package-private - * SecurityFilters enum - * - * @author Josh Cummings - */ -public final class SecurityFiltersAssertions { - - private static Collection ordered = Arrays.asList(SecurityFilters.values()); - - private SecurityFiltersAssertions() { - } - - public static void assertEquals(List filters) { - List expected = ordered.stream().map(SecurityFilters::name).collect(Collectors.toList()); - assertThat(filters).isEqualTo(expected); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/SessionManagementConfigServlet31Tests.java b/config/src/test/java/org/springframework/security/config/http/SessionManagementConfigServlet31Tests.java deleted file mode 100644 index a8324fbd052..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/SessionManagementConfigServlet31Tests.java +++ /dev/null @@ -1,122 +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.http; - -import jakarta.servlet.Filter; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.config.util.InMemoryXmlApplicationContext; -import org.springframework.security.web.servlet.TestMockHttpServletRequests; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Rob Winch - * - */ -public class SessionManagementConfigServlet31Tests { - - // @formatter:off - private static final String XML_AUTHENTICATION_MANAGER = "" - + " " - + " " - + " " - + " " - + " " - + ""; - // @formatter:on - - MockHttpServletRequest request; - - MockHttpServletResponse response; - - MockFilterChain chain; - - ConfigurableApplicationContext context; - - Filter springSecurityFilterChain; - - @BeforeEach - public void setup() { - this.request = new MockHttpServletRequest(); - this.response = new MockHttpServletResponse(); - this.chain = new MockFilterChain(); - } - - @AfterEach - public void teardown() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void changeSessionIdThenPreserveParameters() throws Exception { - MockHttpServletRequest request = TestMockHttpServletRequests.post("/login") - .param("username", "user") - .param("password", "password") - .build(); - request.getSession(); - request.getSession().setAttribute("attribute1", "value1"); - String id = request.getSession().getId(); - // @formatter:off - loadContext("\n" - + " \n" - + " \n" - + " \n" - + " \n" - + " " - + XML_AUTHENTICATION_MANAGER); - // @formatter:on - this.springSecurityFilterChain.doFilter(request, this.response, this.chain); - assertThat(request.getSession().getId()).isNotEqualTo(id); - assertThat(request.getSession().getAttribute("attribute1")).isEqualTo("value1"); - } - - @Test - public void changeSessionId() throws Exception { - MockHttpServletRequest request = TestMockHttpServletRequests.post("/login") - .param("username", "user") - .param("password", "password") - .build(); - request.getSession(); - String id = request.getSession().getId(); - // @formatter:off - loadContext("\n" - + " \n" - + " \n" - + " \n" - + " \n" - + " " - + XML_AUTHENTICATION_MANAGER); - // @formatter:on - this.springSecurityFilterChain.doFilter(request, this.response, this.chain); - assertThat(request.getSession().getId()).isNotEqualTo(id); - } - - private void loadContext(String context) { - this.context = new InMemoryXmlApplicationContext(context); - this.springSecurityFilterChain = this.context.getBean("springSecurityFilterChain", Filter.class); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/SessionManagementConfigTests.java b/config/src/test/java/org/springframework/security/config/http/SessionManagementConfigTests.java deleted file mode 100644 index 1a692391bc2..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/SessionManagementConfigTests.java +++ /dev/null @@ -1,627 +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.http; - -import java.io.IOException; -import java.security.Principal; -import java.util.List; - -import jakarta.servlet.Filter; -import jakarta.servlet.ServletContext; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpServletResponseWrapper; -import org.apache.http.HttpStatus; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.mock.web.MockHttpSession; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.session.SessionRegistry; -import org.springframework.security.util.FieldUtils; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.authentication.RememberMeServices; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.authentication.logout.CompositeLogoutHandler; -import org.springframework.security.web.authentication.logout.LogoutFilter; -import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.security.web.authentication.logout.LogoutSuccessEventPublishingLogoutHandler; -import org.springframework.security.web.authentication.session.SessionAuthenticationException; -import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; -import org.springframework.security.web.context.HttpSessionSecurityContextRepository; -import org.springframework.security.web.session.ConcurrentSessionFilter; -import org.springframework.security.web.session.SessionManagementFilter; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.ResultMatcher; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.context.WebApplicationContext; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests session-related functionality for the <http> namespace element and - * <session-management> - * - * @author Luke Taylor - * @author Rob Winch - * @author Josh Cummings - * @author Onur Kagan Ozcan - * @author Mazen Aissa - */ -@ExtendWith(SpringTestContextExtension.class) -public class SessionManagementConfigTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/SessionManagementConfigTests"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void requestWhenCreateSessionAlwaysThenAlwaysCreatesSession() throws Exception { - this.spring.configLocations(xml("CreateSessionAlways")).autowire(); - MockHttpServletRequest request = get("/").buildRequest(this.servletContext()); - MockHttpServletResponse response = request(request, this.spring.getContext()); - assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK); - assertThat(request.getSession(false)).isNotNull(); - } - - @Test - public void requestWhenCreateSessionIsSetToNeverThenDoesNotCreateSessionOnLoginChallenge() throws Exception { - this.spring.configLocations(xml("CreateSessionNever")).autowire(); - MockHttpServletRequest request = get("/auth").buildRequest(this.servletContext()); - MockHttpServletResponse response = request(request, this.spring.getContext()); - assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); - assertThat(request.getSession(false)).isNull(); - } - - @Test - public void requestWhenCreateSessionIsSetToNeverThenDoesNotCreateSessionOnLogin() throws Exception { - this.spring.configLocations(xml("CreateSessionNever")).autowire(); - // @formatter:off - MockHttpServletRequest request = post("/login") - .param("username", "user") - .param("password", "password") - .buildRequest(this.servletContext()); - // @formatter:on - request = csrf().postProcessRequest(request); - MockHttpServletResponse response = request(request, this.spring.getContext()); - assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); - assertThat(request.getSession(false)).isNull(); - } - - @Test - public void requestWhenCreateSessionIsSetToNeverThenUsesExistingSession() throws Exception { - this.spring.configLocations(xml("CreateSessionNever")).autowire(); - // @formatter:off - MockHttpServletRequest request = post("/login") - .param("username", "user") - .param("password", "password") - .buildRequest(this.servletContext()); - // @formatter:on - request = csrf().postProcessRequest(request); - MockHttpSession session = new MockHttpSession(); - request.setSession(session); - MockHttpServletResponse response = request(request, this.spring.getContext()); - assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); - assertThat(request.getSession(false)).isNotNull(); - assertThat(request.getSession(false) - .getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)).isNotNull(); - } - - @Test - public void requestWhenCreateSessionIsSetToStatelessThenDoesNotCreateSessionOnLoginChallenge() throws Exception { - this.spring.configLocations(xml("CreateSessionStateless")).autowire(); - // @formatter:off - this.mvc.perform(get("/auth")) - .andExpect(status().isFound()) - .andExpect(session().exists(false)); - // @formatter:on - } - - @Test - public void requestWhenCreateSessionIsSetToStatelessThenDoesNotCreateSessionOnLogin() throws Exception { - this.spring.configLocations(xml("CreateSessionStateless")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .param("username", "user") - .param("password", "password") - .with(csrf()); - this.mvc.perform(loginRequest) - .andExpect(status().isFound()) - .andExpect(session().exists(false)); - // @formatter:on - } - - @Test - public void requestWhenCreateSessionIsSetToStatelessThenIgnoresExistingSession() throws Exception { - this.spring.configLocations(xml("CreateSessionStateless")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder loginRequest = post("/login") - .param("username", "user") - .param("password", "password") - .session(new MockHttpSession()) - .with(csrf()); - MvcResult result = this.mvc.perform(loginRequest) - .andExpect(status().isFound()) - .andExpect(session()).andReturn(); - // @formatter:on - assertThat(result.getRequest() - .getSession(false) - .getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)).isNull(); - } - - @Test - public void requestWhenCreateSessionIsSetToIfRequiredThenDoesNotCreateSessionOnPublicInvocation() throws Exception { - this.spring.configLocations(xml("CreateSessionIfRequired")).autowire(); - ServletContext servletContext = this.mvc.getDispatcherServlet().getServletContext(); - MockHttpServletRequest request = get("/").buildRequest(servletContext); - MockHttpServletResponse response = request(request, this.spring.getContext()); - assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_OK); - assertThat(request.getSession(false)).isNull(); - } - - @Test - public void requestWhenCreateSessionIsSetToIfRequiredThenCreatesSessionOnLoginChallenge() throws Exception { - this.spring.configLocations(xml("CreateSessionIfRequired")).autowire(); - ServletContext servletContext = this.mvc.getDispatcherServlet().getServletContext(); - MockHttpServletRequest request = get("/auth").buildRequest(servletContext); - MockHttpServletResponse response = request(request, this.spring.getContext()); - assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); - assertThat(request.getSession(false)).isNotNull(); - } - - @Test - public void requestWhenCreateSessionIsSetToIfRequiredThenCreatesSessionOnLogin() throws Exception { - this.spring.configLocations(xml("CreateSessionIfRequired")).autowire(); - ServletContext servletContext = this.mvc.getDispatcherServlet().getServletContext(); - // @formatter:off - MockHttpServletRequest request = post("/login") - .param("username", "user") - .param("password", "password") - .buildRequest(servletContext); - // @formatter:on - request = csrf().postProcessRequest(request); - MockHttpServletResponse response = request(request, this.spring.getContext()); - assertThat(response.getStatus()).isEqualTo(HttpStatus.SC_MOVED_TEMPORARILY); - assertThat(request.getSession(false)).isNotNull(); - } - - /** - * SEC-1208 - */ - @Test - public void requestWhenRejectingUserBasedOnMaxSessionsExceededThenDoesNotCreateSession() throws Exception { - this.spring.configLocations(xml("Sec1208")).autowire(); - // @formatter:off - this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) - .andExpect(status().isOk()) - .andExpect(session()); - this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) - .andExpect(status().isUnauthorized()) - .andExpect(session().exists(false)); - // @formatter:on - } - - /** - * SEC-2137 - */ - @Test - public void requestWhenSessionFixationProtectionDisabledAndConcurrencyControlEnabledThenSessionNotInvalidated() - throws Exception { - this.spring.configLocations(xml("Sec2137")).autowire(); - MockHttpSession session = new MockHttpSession(); - // @formatter:off - this.mvc.perform(get("/auth").session(session).with(httpBasic("user", "password"))) - .andExpect(status().isOk()) - .andExpect(session().id(session.getId())); - // @formatter:on - } - - @Test - public void autowireWhenExportingSessionRegistryBeanThenAvailableForWiring() { - this.spring.configLocations(xml("ConcurrencyControlSessionRegistryAlias")).autowire(); - this.sessionRegistryIsValid(); - } - - @Test - public void requestWhenExpiredUrlIsSetThenInvalidatesSessionAndRedirects() throws Exception { - this.spring.configLocations(xml("ConcurrencyControlExpiredUrl")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = get("/auth") - .session(expiredSession()) - .with(httpBasic("user", "password")); - this.mvc.perform(request) - .andExpect(redirectedUrl("/expired")) - .andExpect(session().exists(false)); - // @formatter:on - } - - @Test - public void requestWhenConcurrencyControlAndCustomLogoutHandlersAreSetThenAllAreInvokedWhenSessionExpires() - throws Exception { - this.spring.configLocations(xml("ConcurrencyControlLogoutAndRememberMeHandlers")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = get("/auth") - .session(expiredSession()) - .with(httpBasic("user", "password")); - this.mvc.perform(request) - .andExpect(status().isOk()) - .andExpect(cookie().maxAge("testCookie", 0)) - .andExpect(cookie().exists("rememberMeCookie")) - .andExpect(session().valid(true)); - // @formatter:on - } - - @Test - public void requestWhenConcurrencyControlAndRememberMeAreSetThenInvokedWhenSessionExpires() throws Exception { - this.spring.configLocations(xml("ConcurrencyControlRememberMeHandler")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = get("/auth") - .session(expiredSession()) - .with(httpBasic("user", "password")); - this.mvc.perform(request) - .andExpect(status().isOk()).andExpect(cookie().exists("rememberMeCookie")) - .andExpect(session().exists(false)); - // @formatter:on - } - - /** - * SEC-2057 - */ - @Test - public void autowireWhenConcurrencyControlIsSetThenLogoutHandlersGetAuthenticationObject() throws Exception { - this.spring.configLocations(xml("ConcurrencyControlCustomLogoutHandler")).autowire(); - MvcResult result = this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) - .andExpect(session()) - .andReturn(); - MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); - SessionRegistry sessionRegistry = this.spring.getContext().getBean(SessionRegistry.class); - sessionRegistry.getSessionInformation(session.getId()).expireNow(); - // @formatter:off - this.mvc.perform(get("/auth").session(session)) - .andExpect(header().string("X-Username", "user")); - // @formatter:on - } - - @Test - public void requestWhenConcurrencyControlIsSetThenDefaultsToResponseBodyExpirationResponse() throws Exception { - this.spring.configLocations(xml("ConcurrencyControlSessionRegistryAlias")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder request = get("/auth") - .session(expiredSession()) - .with(httpBasic("user", "password")); - this.mvc.perform(request) - .andExpect(content().string("This session has been expired (possibly due to multiple concurrent " - + "logins being attempted as the same user).")); - // @formatter:on - } - - @Test - public void requestWhenCustomSessionAuthenticationStrategyThenInvokesOnAuthentication() throws Exception { - this.spring.configLocations(xml("SessionAuthenticationStrategyRef")).autowire(); - // @formatter:off - this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) - .andExpect(status().isIAmATeapot()); - // @formatter:on - } - - @Test - public void autowireWhenSessionRegistryRefIsSetThenAvailableForWiring() { - this.spring.configLocations(xml("ConcurrencyControlSessionRegistryRef")).autowire(); - this.sessionRegistryIsValid(); - } - - @Test - public void requestWhenMaxSessionsIsSetThenErrorsWhenExceeded() throws Exception { - this.spring.configLocations(xml("ConcurrencyControlMaxSessions")).autowire(); - // @formatter:off - this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) - .andExpect(status().isOk()); - this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) - .andExpect(status().isOk()); - this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) - .andExpect(redirectedUrl("/max-exceeded")); - // @formatter:on - } - - @Test - public void requestWhenMaxSessionsIsSetWithPlaceHolderThenErrorsWhenExceeded() throws Exception { - System.setProperty("sessionManagement.maxSessions", "1"); - this.spring.configLocations(xml("ConcurrencyControlMaxSessionsPlaceHolder")).autowire(); - // @formatter:off - this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) - .andExpect(status().isOk()); - this.mvc.perform(get("/auth").with(httpBasic("user", "password"))) - .andExpect(redirectedUrl("/max-exceeded")); - // @formatter:on - } - - @Test - public void autowireWhenSessionFixationProtectionIsNoneAndCsrfDisabledThenSessionManagementFilterIsNotWired() { - this.spring.configLocations(xml("NoSessionManagementFilter")).autowire(); - assertThat(this.getFilter(SessionManagementFilter.class)).isNull(); - } - - @Test - public void requestWhenSessionFixationProtectionIsNoneThenSessionNotInvalidated() throws Exception { - this.spring.configLocations(xml("SessionFixationProtectionNone")).autowire(); - MockHttpSession session = new MockHttpSession(); - String sessionId = session.getId(); - // @formatter:off - this.mvc.perform(get("/auth").session(session).with(httpBasic("user", "password"))) - .andExpect(session().id(sessionId)); - // @formatter:on - } - - @Test - public void requestWhenSessionFixationProtectionIsMigrateSessionThenSessionIsReplaced() throws Exception { - this.spring.configLocations(xml("SessionFixationProtectionMigrateSession")).autowire(); - MockHttpSession session = new MockHttpSession(); - String sessionId = session.getId(); - // @formatter:off - MvcResult result = this.mvc.perform(get("/auth").session(session).with(httpBasic("user", "password"))) - .andExpect(session()) - .andReturn(); - // @formatter:on - assertThat(result.getRequest().getSession(false).getId()).isNotEqualTo(sessionId); - } - - @Test - public void requestWhenSessionFixationProtectionIsNoneAndInvalidSessionUrlIsSetThenStillRedirectsOnInvalidSession() - throws Exception { - this.spring.configLocations(xml("SessionFixationProtectionNoneWithInvalidSessionUrl")).autowire(); - // @formatter:off - MockHttpServletRequestBuilder authRequest = get("/auth") - .with((request) -> { - request.setRequestedSessionId("1"); - request.setRequestedSessionIdValid(false); - return request; - }); - this.mvc.perform(authRequest) - .andExpect(redirectedUrl("/timeoutUrl")); - // @formatter:on - } - - private void sessionRegistryIsValid() { - SessionRegistry sessionRegistry = this.spring.getContext().getBean("sessionRegistry", SessionRegistry.class); - assertThat(sessionRegistry).isNotNull(); - assertThat(this.getFilter(ConcurrentSessionFilter.class)).returns(sessionRegistry, - this::extractSessionRegistry); - assertThat(this.getFilter(UsernamePasswordAuthenticationFilter.class)).returns(sessionRegistry, - this::extractSessionRegistry); - // SEC-1143 - assertThat(this.getFilter(SessionManagementFilter.class)).returns(sessionRegistry, - this::extractSessionRegistry); - } - - private SessionRegistry extractSessionRegistry(ConcurrentSessionFilter filter) { - return getFieldValue(filter, "sessionRegistry"); - } - - private SessionRegistry extractSessionRegistry(UsernamePasswordAuthenticationFilter filter) { - SessionAuthenticationStrategy strategy = getFieldValue(filter, "sessionStrategy"); - List strategies = getFieldValue(strategy, "delegateStrategies"); - return getFieldValue(strategies.get(0), "sessionRegistry"); - } - - private SessionRegistry extractSessionRegistry(SessionManagementFilter filter) { - SessionAuthenticationStrategy strategy = getFieldValue(filter, "sessionAuthenticationStrategy"); - List strategies = getFieldValue(strategy, "delegateStrategies"); - return getFieldValue(strategies.get(0), "sessionRegistry"); - } - - private T getFieldValue(Object target, String fieldName) { - try { - return (T) FieldUtils.getFieldValue(target, fieldName); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - private static SessionResultMatcher session() { - return new SessionResultMatcher(); - } - - /** - * SEC-2680 - */ - @Test - public void checkConcurrencyAndLogoutFilterHasSameSizeAndHasLogoutSuccessEventPublishingLogoutHandler() { - this.spring.configLocations(xml("ConcurrencyControlLogoutAndRememberMeHandlers")).autowire(); - ConcurrentSessionFilter concurrentSessionFilter = getFilter(ConcurrentSessionFilter.class); - LogoutFilter logoutFilter = getFilter(LogoutFilter.class); - LogoutHandler csfLogoutHandler = getFieldValue(concurrentSessionFilter, "handlers"); - LogoutHandler lfLogoutHandler = getFieldValue(logoutFilter, "handler"); - assertThat(csfLogoutHandler).isInstanceOf(CompositeLogoutHandler.class); - assertThat(lfLogoutHandler).isInstanceOf(CompositeLogoutHandler.class); - List csfLogoutHandlers = getFieldValue(csfLogoutHandler, "logoutHandlers"); - List lfLogoutHandlers = getFieldValue(lfLogoutHandler, "logoutHandlers"); - assertThat(csfLogoutHandlers).hasSameSizeAs(lfLogoutHandlers); - assertThat(csfLogoutHandlers).hasAtLeastOneElementOfType(LogoutSuccessEventPublishingLogoutHandler.class); - assertThat(lfLogoutHandlers).hasAtLeastOneElementOfType(LogoutSuccessEventPublishingLogoutHandler.class); - } - - private static MockHttpServletResponse request(MockHttpServletRequest request, ApplicationContext context) - throws IOException, ServletException { - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChainProxy proxy = context.getBean(FilterChainProxy.class); - proxy.doFilter(request, new EncodeUrlDenyingHttpServletResponseWrapper(response), (req, resp) -> { - }); - return response; - } - - private MockHttpSession expiredSession() { - MockHttpSession session = new MockHttpSession(); - SessionRegistry sessionRegistry = this.spring.getContext().getBean(SessionRegistry.class); - sessionRegistry.registerNewSession(session.getId(), "user"); - sessionRegistry.getSessionInformation(session.getId()).expireNow(); - return session; - } - - private T getFilter(Class filterClass) { - return (T) getFilters().stream().filter(filterClass::isInstance).findFirst().orElse(null); - } - - private List getFilters() { - FilterChainProxy proxy = this.spring.getContext().getBean(FilterChainProxy.class); - return proxy.getFilters("/"); - } - - private ServletContext servletContext() { - WebApplicationContext context = this.spring.getContext(); - return context.getServletContext(); - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - static class TeapotSessionAuthenticationStrategy implements SessionAuthenticationStrategy { - - @Override - public void onAuthentication(Authentication authentication, HttpServletRequest request, - HttpServletResponse response) throws SessionAuthenticationException { - response.setStatus(org.springframework.http.HttpStatus.I_AM_A_TEAPOT.value()); - } - - } - - static class CustomRememberMeServices implements RememberMeServices, LogoutHandler { - - @Override - public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response) { - return null; - } - - @Override - public void loginFail(HttpServletRequest request, HttpServletResponse response) { - } - - @Override - public void loginSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication successfulAuthentication) { - } - - @Override - public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { - response.addHeader("X-Username", authentication.getName()); - } - - } - - @RestController - static class BasicController { - - @GetMapping("/") - String ok() { - return "ok"; - } - - @GetMapping("/auth") - String auth(Principal principal) { - return principal.getName(); - } - - } - - private static class SessionResultMatcher implements ResultMatcher { - - private String id; - - private Boolean valid; - - private Boolean exists = true; - - ResultMatcher exists(boolean exists) { - this.exists = exists; - return this; - } - - ResultMatcher valid(boolean valid) { - this.valid = valid; - return this.exists(true); - } - - ResultMatcher id(String id) { - this.id = id; - return this.exists(true); - } - - @Override - public void match(MvcResult result) { - if (!this.exists) { - assertThat(result.getRequest().getSession(false)).isNull(); - return; - } - assertThat(result.getRequest().getSession(false)).isNotNull(); - MockHttpSession session = (MockHttpSession) result.getRequest().getSession(false); - if (this.valid != null) { - if (this.valid) { - assertThat(session.isInvalid()).isFalse(); - } - else { - assertThat(session.isInvalid()).isTrue(); - } - } - if (this.id != null) { - assertThat(session.getId()).isEqualTo(this.id); - } - } - - } - - private static class EncodeUrlDenyingHttpServletResponseWrapper extends HttpServletResponseWrapper { - - EncodeUrlDenyingHttpServletResponseWrapper(HttpServletResponse response) { - super(response); - } - - @Override - public String encodeURL(String url) { - throw new RuntimeException("Unexpected invocation of encodeURL"); - } - - @Override - public String encodeRedirectURL(String url) { - throw new RuntimeException("Unexpected invocation of encodeURL"); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests.java b/config/src/test/java/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests.java deleted file mode 100644 index 97c18412b74..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests.java +++ /dev/null @@ -1,103 +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.http; - -import java.util.Collection; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.Transient; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.MvcResult; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; - -/** - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class SessionManagementConfigTransientAuthenticationTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/SessionManagementConfigTransientAuthenticationTests"; - - @Autowired - MockMvc mvc; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Test - public void postWhenTransientAuthenticationThenNoSessionCreated() throws Exception { - this.spring.configLocations(this.xml("WithTransientAuthentication")).autowire(); - MvcResult result = this.mvc.perform(post("/login")).andReturn(); - assertThat(result.getRequest().getSession(false)).isNull(); - } - - @Test - public void postWhenTransientAuthenticationThenAlwaysSessionOverrides() throws Exception { - this.spring.configLocations(this.xml("CreateSessionAlwaysWithTransientAuthentication")).autowire(); - MvcResult result = this.mvc.perform(post("/login")).andReturn(); - assertThat(result.getRequest().getSession(false)).isNotNull(); - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - static class TransientAuthenticationProvider implements AuthenticationProvider { - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - return new SomeTransientAuthentication(); - } - - @Override - public boolean supports(Class authentication) { - return true; - } - - } - - @Transient - static class SomeTransientAuthentication extends AbstractAuthenticationToken { - - SomeTransientAuthentication() { - super((Collection) null); - } - - @Override - public Object getCredentials() { - return null; - } - - @Override - public Object getPrincipal() { - return null; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/WebConfigUtilsTests.java b/config/src/test/java/org/springframework/security/config/http/WebConfigUtilsTests.java deleted file mode 100644 index db576ba40f2..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/WebConfigUtilsTests.java +++ /dev/null @@ -1,44 +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.http; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.beans.factory.xml.ParserContext; - -import static org.mockito.Mockito.verifyNoMoreInteractions; - -@ExtendWith(MockitoExtension.class) -public class WebConfigUtilsTests { - - public static final String URL = "/url"; - - @Mock - private ParserContext parserContext; - - // SEC-1980 - @Test - public void validateHttpRedirectSpELNoParserWarning() { - WebConfigUtils.validateHttpRedirect("#{T(org.springframework.security.config.http.WebConfigUtilsTest).URL}", - this.parserContext, "fakeSource"); - verifyNoMoreInteractions(this.parserContext); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/WellKnownChangePasswordBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/http/WellKnownChangePasswordBeanDefinitionParserTests.java deleted file mode 100644 index 44f005fb011..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/WellKnownChangePasswordBeanDefinitionParserTests.java +++ /dev/null @@ -1,68 +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.http; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.test.web.servlet.MockMvc; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * Tests for {@link WellKnownChangePasswordBeanDefinitionParser}. - * - * @author Evgeniy Cheban - */ -@ExtendWith(SpringTestContextExtension.class) -public class WellKnownChangePasswordBeanDefinitionParserTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/WellKnownChangePasswordBeanDefinitionParserTests"; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - MockMvc mvc; - - @Test - public void whenChangePasswordPageNotSetThenDefaultChangePasswordPageUsed() throws Exception { - this.spring.configLocations(xml("DefaultChangePasswordPage")).autowire(); - - this.mvc.perform(get("/.well-known/change-password")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/change-password")); - } - - @Test - public void whenChangePasswordPageSetThenSpecifiedChangePasswordPageUsed() throws Exception { - this.spring.configLocations(xml("CustomChangePasswordPage")).autowire(); - - this.mvc.perform(get("/.well-known/change-password")) - .andExpect(status().isFound()) - .andExpect(redirectedUrl("/custom-change-password-page")); - } - - private String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/customconfigurer/CustomConfigurer.java b/config/src/test/java/org/springframework/security/config/http/customconfigurer/CustomConfigurer.java deleted file mode 100644 index de1c15f01cf..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/customconfigurer/CustomConfigurer.java +++ /dev/null @@ -1,70 +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.http.customconfigurer; - -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.ApplicationContext; -import org.springframework.security.config.annotation.SecurityConfigurerAdapter; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer; -import org.springframework.security.web.DefaultSecurityFilterChain; - -import static org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher.pathPattern; - -/** - * @author Rob Winch - * - */ -public class CustomConfigurer extends SecurityConfigurerAdapter { - - @Value("${permitAllPattern}") - private String permitAllPattern; - - private String loginPage = "/login"; - - @SuppressWarnings("unchecked") - @Override - public void init(HttpSecurity http) { - // autowire this bean - ApplicationContext context = http.getSharedObject(ApplicationContext.class); - context.getAutowireCapableBeanFactory().autowireBean(this); - // @formatter:off - http - .authorizeHttpRequests((requests) -> requests - .requestMatchers(pathPattern(this.permitAllPattern)).permitAll() - .anyRequest().authenticated()); - // @formatter:on - if (http.getConfigurer(FormLoginConfigurer.class) == null) { - // only apply if formLogin() was not invoked by the user - // @formatter:off - http - .formLogin((login) -> login - .loginPage(this.loginPage)); - // @formatter:on - } - } - - public CustomConfigurer loginPage(String loginPage) { - this.loginPage = loginPage; - return this; - } - - public static CustomConfigurer customConfigurer() { - return new CustomConfigurer(); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/http/customconfigurer/CustomHttpSecurityConfigurerTests.java b/config/src/test/java/org/springframework/security/config/http/customconfigurer/CustomHttpSecurityConfigurerTests.java deleted file mode 100644 index 1a34c2e7592..00000000000 --- a/config/src/test/java/org/springframework/security/config/http/customconfigurer/CustomHttpSecurityConfigurerTests.java +++ /dev/null @@ -1,167 +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.http.customconfigurer; - -import java.util.Properties; - -import jakarta.servlet.http.HttpServletResponse; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; -import org.springframework.mock.web.MockFilterChain; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.web.FilterChainProxy; -import org.springframework.security.web.SecurityFilterChain; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Rob Winch - * - */ -public class CustomHttpSecurityConfigurerTests { - - @Autowired - ConfigurableApplicationContext context; - - @Autowired - FilterChainProxy springSecurityFilterChain; - - MockHttpServletRequest request; - - MockHttpServletResponse response; - - MockFilterChain chain; - - @BeforeEach - public void setup() { - this.request = new MockHttpServletRequest(); - this.response = new MockHttpServletResponse(); - this.chain = new MockFilterChain(); - this.request.setMethod("GET"); - } - - @AfterEach - public void cleanup() { - if (this.context != null) { - this.context.close(); - } - } - - @Test - public void customConfiguerPermitAll() throws Exception { - loadContext(Config.class); - this.request.setRequestURI("/public/something"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - } - - @Test - public void customConfiguerFormLogin() throws Exception { - loadContext(Config.class); - this.request.setRequestURI("/requires-authentication"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getRedirectedUrl()).endsWith("/custom"); - } - - @Test - public void customConfiguerCustomizeDisablesCsrf() throws Exception { - loadContext(ConfigCustomize.class); - this.request.setRequestURI("/public/something"); - this.request.setMethod("POST"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK); - } - - @Test - public void customConfiguerCustomizeFormLogin() throws Exception { - loadContext(ConfigCustomize.class); - this.request.setRequestURI("/requires-authentication"); - this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain); - assertThat(this.response.getRedirectedUrl()).endsWith("/other"); - } - - private void loadContext(Class clazz) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(clazz); - context.getAutowireCapableBeanFactory().autowireBean(this); - } - - @Configuration - @EnableWebSecurity - static class Config { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - return http - .with(CustomConfigurer.customConfigurer(), (c) -> c.loginPage("/custom")) - .build(); - // @formatter:on - } - - @Bean - static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() { - // Typically externalize this as a properties file - Properties properties = new Properties(); - properties.setProperty("permitAllPattern", "/public/**"); - PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer(); - propertyPlaceholderConfigurer.setProperties(properties); - return propertyPlaceholderConfigurer; - } - - } - - @Configuration - @EnableWebSecurity - static class ConfigCustomize { - - @Bean - SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - // @formatter:off - http - .with(CustomConfigurer.customConfigurer(), Customizer.withDefaults()) - .csrf((csrf) -> csrf.disable()) - .formLogin((login) -> login - .loginPage("/other")); - return http.build(); - // @formatter:on - } - - @Bean - static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() { - // Typically externalize this as a properties file - Properties properties = new Properties(); - properties.setProperty("permitAllPattern", "/public/**"); - PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer(); - propertyPlaceholderConfigurer.setProperties(properties); - return propertyPlaceholderConfigurer; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/method/Contact.java b/config/src/test/java/org/springframework/security/config/method/Contact.java deleted file mode 100644 index fa43c011f0f..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/Contact.java +++ /dev/null @@ -1,41 +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.method; - -/** - * @author Rob Winch - * - */ -public class Contact { - - private String name; - - /** - * @param name - */ - public Contact(String name) { - this.name = name; - } - - /** - * @return the name - */ - public String getName() { - return this.name; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/method/ContactPermission.java b/config/src/test/java/org/springframework/security/config/method/ContactPermission.java deleted file mode 100644 index e7e8b2b1356..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/ContactPermission.java +++ /dev/null @@ -1,32 +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.method; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import org.springframework.security.access.prepost.PreAuthorize; - -/** - * @author Rob Winch - * - */ -@Retention(RetentionPolicy.RUNTIME) -@PreAuthorize("#contact.name == authentication.name") -public @interface ContactPermission { - -} diff --git a/config/src/test/java/org/springframework/security/config/method/GlobalMethodSecurityBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/method/GlobalMethodSecurityBeanDefinitionParserTests.java deleted file mode 100644 index 77990def705..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/GlobalMethodSecurityBeanDefinitionParserTests.java +++ /dev/null @@ -1,445 +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.method; - -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import org.springframework.aop.Advisor; -import org.springframework.aop.framework.Advised; -import org.springframework.beans.BeansException; -import org.springframework.beans.MutablePropertyValues; -import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.support.AbstractXmlApplicationContext; -import org.springframework.context.support.StaticApplicationContext; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.ConfigAttribute; -import org.springframework.security.access.SecurityConfig; -import org.springframework.security.access.annotation.BusinessService; -import org.springframework.security.access.annotation.ExpressionProtectedBusinessServiceImpl; -import org.springframework.security.access.intercept.AfterInvocationProviderManager; -import org.springframework.security.access.intercept.RunAsManagerImpl; -import org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor; -import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor; -import org.springframework.security.access.prepost.PostInvocationAdviceProvider; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter; -import org.springframework.security.access.vote.AffirmativeBased; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.ConfigTestUtils; -import org.springframework.security.config.PostProcessedMockUserDetailsService; -import org.springframework.security.config.util.InMemoryXmlApplicationContext; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.util.FieldUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Ben Alex - * @author Luke Taylor - */ -public class GlobalMethodSecurityBeanDefinitionParserTests { - - private final UsernamePasswordAuthenticationToken bob = UsernamePasswordAuthenticationToken.unauthenticated("bob", - "bobspassword"); - - private AbstractXmlApplicationContext appContext; - - private BusinessService target; - - public void loadContext() { - // @formatter:off - setContext("" - + "" - + " " - + " " - + "" - + ConfigTestUtils.AUTH_PROVIDER_XML); - // @formatter:on - this.target = (BusinessService) this.appContext.getBean("target"); - } - - @AfterEach - public void closeAppContext() { - if (this.appContext != null) { - this.appContext.close(); - this.appContext = null; - } - SecurityContextHolder.clearContext(); - this.target = null; - } - - @Test - public void targetShouldPreventProtectedMethodInvocationWithNoContext() { - loadContext(); - assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) - .isThrownBy(this.target::someUserMethod1); - } - - @Test - public void targetShouldAllowProtectedMethodInvocationWithCorrectRole() { - loadContext(); - UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.unauthenticated("user", - "password"); - SecurityContextHolder.getContext().setAuthentication(token); - this.target.someUserMethod1(); - // SEC-1213. Check the order - Advisor[] advisors = ((Advised) this.target).getAdvisors(); - assertThat(advisors).hasSize(1); - assertThat(((MethodSecurityMetadataSourceAdvisor) advisors[0]).getOrder()).isEqualTo(1001); - } - - @Test - public void targetShouldPreventProtectedMethodInvocationWithIncorrectRole() { - loadContext(); - TestingAuthenticationToken token = new TestingAuthenticationToken("Test", "Password", "ROLE_SOMEOTHERROLE"); - token.setAuthenticated(true); - SecurityContextHolder.getContext().setAuthentication(token); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.target::someAdminMethod); - } - - @Test - public void doesntInterfereWithBeanPostProcessing() { - // @formatter:off - setContext("" - + "" - + "" - + " " - + "" - + ""); - // @formatter:on - PostProcessedMockUserDetailsService service = (PostProcessedMockUserDetailsService) this.appContext - .getBean("myUserService"); - assertThat(service.getPostProcessorWasHere()).isEqualTo("Hello from the post processor!"); - } - - @Test - public void worksWithAspectJAutoproxy() { - // @formatter:off - setContext("" - + " " - + "" - + "" - + "" - + "" - + " " - + ""); - // @formatter:on - UserDetailsService service = (UserDetailsService) this.appContext.getBean("myUserService"); - UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", - "Password", AuthorityUtils.createAuthorityList("ROLE_SOMEOTHERROLE")); - SecurityContextHolder.getContext().setAuthentication(token); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> service.loadUserByUsername("notused")); - } - - @Test - public void supportsMethodArgumentsInPointcut() { - // @formatter:off - setContext("" - + "" - + " " - + " " - + "" - + ConfigTestUtils.AUTH_PROVIDER_XML); - // @formatter:on - SecurityContextHolder.getContext() - .setAuthentication(UsernamePasswordAuthenticationToken.unauthenticated("user", "password")); - this.target = (BusinessService) this.appContext.getBean("target"); - // someOther(int) should not be matched by someOther(String), but should require - // ROLE_USER - this.target.someOther(0); - // String version should required admin role - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> this.target.someOther("somestring")); - } - - @Test - public void supportsBooleanPointcutExpressions() { - // @formatter:off - setContext("" - + "" - + " " - + "" - + ConfigTestUtils.AUTH_PROVIDER_XML); - // @formatter:on - this.target = (BusinessService) this.appContext.getBean("target"); - // String method should not be protected - this.target.someOther("somestring"); - // All others should require ROLE_USER - assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) - .isThrownBy(() -> this.target.someOther(0)); - SecurityContextHolder.getContext() - .setAuthentication(UsernamePasswordAuthenticationToken.unauthenticated("user", "password")); - this.target.someOther(0); - } - - @Test - public void duplicateElementCausesError() { - assertThatExceptionOfType(BeanDefinitionParsingException.class) - .isThrownBy(() -> setContext("" + "")); - } - - // Expression configuration tests - @SuppressWarnings("unchecked") - @Test - public void expressionVoterAndAfterInvocationProviderUseSameExpressionHandlerInstance() throws Exception { - setContext("" + ConfigTestUtils.AUTH_PROVIDER_XML); - AffirmativeBased adm = (AffirmativeBased) this.appContext.getBeansOfType(AffirmativeBased.class) - .values() - .toArray()[0]; - List voters = (List) FieldUtils.getFieldValue(adm, "decisionVoters"); - PreInvocationAuthorizationAdviceVoter mev = (PreInvocationAuthorizationAdviceVoter) voters.get(0); - MethodSecurityMetadataSourceAdvisor msi = (MethodSecurityMetadataSourceAdvisor) this.appContext - .getBeansOfType(MethodSecurityMetadataSourceAdvisor.class) - .values() - .toArray()[0]; - AfterInvocationProviderManager pm = (AfterInvocationProviderManager) ((MethodSecurityInterceptor) msi - .getAdvice()).getAfterInvocationManager(); - PostInvocationAdviceProvider aip = (PostInvocationAdviceProvider) pm.getProviders().get(0); - assertThat(FieldUtils.getFieldValue(mev, "preAdvice.expressionHandler")) - .isSameAs(FieldUtils.getFieldValue(aip, "postAdvice.expressionHandler")); - } - - @Test - public void accessIsDeniedForHasRoleExpression() { - // @formatter:off - setContext("" - + "" - + ConfigTestUtils.AUTH_PROVIDER_XML); - // @formatter:on - SecurityContextHolder.getContext().setAuthentication(this.bob); - this.target = (BusinessService) this.appContext.getBean("target"); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.target::someAdminMethod); - } - - @Test - public void beanNameExpressionPropertyIsSupported() { - // @formatter:off - setContext("" - + "" - + " " - + "" - + "" - + ConfigTestUtils.AUTH_PROVIDER_XML); - // @formatter:on - SecurityContextHolder.getContext().setAuthentication(this.bob); - ExpressionProtectedBusinessServiceImpl target = (ExpressionProtectedBusinessServiceImpl) this.appContext - .getBean("target"); - target.methodWithBeanNamePropertyAccessExpression("x"); - } - - @Test - public void preAndPostFilterAnnotationsWorkWithLists() { - // @formatter:off - setContext("" - + "" - + ConfigTestUtils.AUTH_PROVIDER_XML); - // @formatter:on - SecurityContextHolder.getContext().setAuthentication(this.bob); - this.target = (BusinessService) this.appContext.getBean("target"); - List arg = new ArrayList<>(); - arg.add("joe"); - arg.add("bob"); - arg.add("sam"); - List result = this.target.methodReturningAList(arg); - // Expression is (filterObject == name or filterObject == 'sam'), so "joe" should - // be gone after pre-filter - // PostFilter should remove sam from the return object - assertThat(result).hasSize(1); - assertThat(result.get(0)).isEqualTo("bob"); - } - - @Test - public void prePostFilterAnnotationWorksWithArrays() { - // @formatter:off - setContext("" - + "" - + ConfigTestUtils.AUTH_PROVIDER_XML); - // @formatter:on - SecurityContextHolder.getContext().setAuthentication(this.bob); - this.target = (BusinessService) this.appContext.getBean("target"); - Object[] arg = new String[] { "joe", "bob", "sam" }; - Object[] result = this.target.methodReturningAnArray(arg); - assertThat(result).hasSize(1); - assertThat(result[0]).isEqualTo("bob"); - } - - // SEC-1392 - @Test - public void customPermissionEvaluatorIsSupported() { - // @formatter:off - setContext("" - + " " - + "" - + "" - + " " - + "" - + "" - + ConfigTestUtils.AUTH_PROVIDER_XML); - // @formatter:on - } - - // SEC-1450 - @Test - @SuppressWarnings("unchecked") - public void genericsAreMatchedByProtectPointcut() { - // @formatter:off - setContext( - "" - + "" - + " " - + "" - + ConfigTestUtils.AUTH_PROVIDER_XML); - // @formatter:on - Foo foo = (Foo) this.appContext.getBean("target"); - assertThatExceptionOfType(AuthenticationException.class).isThrownBy(() -> foo.foo(new SecurityConfig("A"))); - } - - // SEC-1448 - @Test - @SuppressWarnings("unchecked") - public void genericsMethodArgumentNamesAreResolved() { - // @formatter:off - setContext("" - + "" - + ConfigTestUtils.AUTH_PROVIDER_XML); - // @formatter:on - SecurityContextHolder.getContext().setAuthentication(this.bob); - Foo foo = (Foo) this.appContext.getBean("target"); - foo.foo(new SecurityConfig("A")); - } - - @Test - public void runAsManagerIsSetCorrectly() throws Exception { - StaticApplicationContext parent = new StaticApplicationContext(); - MutablePropertyValues props = new MutablePropertyValues(); - props.addPropertyValue("key", "blah"); - parent.registerSingleton("runAsMgr", RunAsManagerImpl.class, props); - parent.refresh(); - setContext("" + ConfigTestUtils.AUTH_PROVIDER_XML, - parent); - RunAsManagerImpl ram = (RunAsManagerImpl) this.appContext.getBean("runAsMgr"); - MethodSecurityMetadataSourceAdvisor msi = (MethodSecurityMetadataSourceAdvisor) this.appContext - .getBeansOfType(MethodSecurityMetadataSourceAdvisor.class) - .values() - .toArray()[0]; - assertThat(ram).isSameAs(FieldUtils.getFieldValue(msi.getAdvice(), "runAsManager")); - } - - @Test - @SuppressWarnings("unchecked") - public void supportsExternalMetadataSource() { - // @formatter:off - setContext("" - + "" - + " " - + "" - + "" - + ConfigTestUtils.AUTH_PROVIDER_XML); - // @formatter:on - // External MDS should take precedence over PreAuthorize - SecurityContextHolder.getContext().setAuthentication(this.bob); - Foo foo = (Foo) this.appContext.getBean("target"); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> foo.foo(new SecurityConfig("A"))); - SecurityContextHolder.getContext() - .setAuthentication(UsernamePasswordAuthenticationToken.unauthenticated("admin", "password")); - foo.foo(new SecurityConfig("A")); - } - - @Test - public void supportsCustomAuthenticationManager() { - // @formatter:off - setContext("" - + "" - + " " - + "" - + "" - + "" - + " " - + "" - + ConfigTestUtils.AUTH_PROVIDER_XML); - // @formatter:on - SecurityContextHolder.getContext().setAuthentication(this.bob); - Foo foo = (Foo) this.appContext.getBean("target"); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> foo.foo(new SecurityConfig("A"))); - SecurityContextHolder.getContext() - .setAuthentication(UsernamePasswordAuthenticationToken.unauthenticated("admin", "password")); - foo.foo(new SecurityConfig("A")); - } - - private void setContext(String context) { - this.appContext = new InMemoryXmlApplicationContext(context); - } - - private void setContext(String context, ApplicationContext parent) { - this.appContext = new InMemoryXmlApplicationContext(context, parent); - } - - static class CustomAuthManager implements AuthenticationManager, ApplicationContextAware { - - private String beanName; - - private AuthenticationManager authenticationManager; - - CustomAuthManager(String beanName) { - this.beanName = beanName; - } - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - return this.authenticationManager.authenticate(authentication); - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.authenticationManager = applicationContext.getBean(this.beanName, AuthenticationManager.class); - } - - } - - interface Foo { - - void foo(T action); - - } - - public static class ConcreteFoo implements Foo { - - @Override - @PreAuthorize("#action.attribute == 'A'") - public void foo(SecurityConfig action) { - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/method/InterceptMethodsBeanDefinitionDecoratorTests.java b/config/src/test/java/org/springframework/security/config/method/InterceptMethodsBeanDefinitionDecoratorTests.java deleted file mode 100644 index 4facd97bd3e..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/InterceptMethodsBeanDefinitionDecoratorTests.java +++ /dev/null @@ -1,181 +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.method; - -import org.aopalliance.intercept.MethodInvocation; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.ApplicationContext; -import org.springframework.context.ApplicationContextAware; -import org.springframework.context.ApplicationListener; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.authentication.event.AuthenticationSuccessEvent; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.config.TestBusinessBean; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.verify; - -/** - * @author Luke Taylor - */ -@ExtendWith(SpringExtension.class) -@ContextConfiguration(locations = "classpath:org/springframework/security/config/method-security.xml") -public class InterceptMethodsBeanDefinitionDecoratorTests implements ApplicationContextAware { - - @Autowired - @Qualifier("target") - private TestBusinessBean target; - - @Autowired - @Qualifier("transactionalTarget") - private TestBusinessBean transactionalTarget; - - @Autowired - @Qualifier("targetAuthorizationManager") - private TestBusinessBean targetAuthorizationManager; - - @Autowired - @Qualifier("transactionalTargetAuthorizationManager") - private TestBusinessBean transactionalTargetAuthorizationManager; - - @Autowired - @Qualifier("targetCustomAuthorizationManager") - private TestBusinessBean targetCustomAuthorizationManager; - - @Autowired - private AuthorizationManager mockAuthorizationManager; - - private ApplicationContext appContext; - - @BeforeAll - public static void loadContext() { - // Set value for placeholder - System.setProperty("admin.role", "ROLE_ADMIN"); - } - - @AfterEach - public void clearContext() { - SecurityContextHolder.clearContext(); - } - - @Test - public void targetDoesntLoseApplicationListenerInterface() { - assertThat(this.appContext.getBeansOfType(ApplicationListener.class)).isNotEmpty(); - assertThat(this.appContext.getBeanNamesForType(ApplicationListener.class)).isNotEmpty(); - this.appContext.publishEvent(new AuthenticationSuccessEvent(new TestingAuthenticationToken("user", ""))); - assertThat(this.target).isInstanceOf(ApplicationListener.class); - assertThat(this.targetAuthorizationManager).isInstanceOf(ApplicationListener.class); - assertThat(this.targetCustomAuthorizationManager).isInstanceOf(ApplicationListener.class); - } - - @Test - public void targetShouldAllowUnprotectedMethodInvocationWithNoContext() { - this.target.unprotected(); - } - - @Test - public void targetShouldPreventProtectedMethodInvocationWithNoContext() { - assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) - .isThrownBy(this.target::doSomething); - } - - @Test - public void targetShouldAllowProtectedMethodInvocationWithCorrectRole() { - UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", - "Password", AuthorityUtils.createAuthorityList("ROLE_USER")); - SecurityContextHolder.getContext().setAuthentication(token); - this.target.doSomething(); - } - - @Test - public void targetShouldPreventProtectedMethodInvocationWithIncorrectRole() { - UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", - "Password", AuthorityUtils.createAuthorityList("ROLE_SOMEOTHERROLE")); - SecurityContextHolder.getContext().setAuthentication(token); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.target::doSomething); - } - - @Test - public void transactionalMethodsShouldBeSecured() { - assertThatExceptionOfType(AuthenticationException.class).isThrownBy(this.transactionalTarget::doSomething); - } - - @Test - public void targetAuthorizationManagerShouldAllowUnprotectedMethodInvocationWithNoContext() { - this.targetAuthorizationManager.unprotected(); - } - - @Test - public void targetAuthorizationManagerShouldPreventProtectedMethodInvocationWithNoContext() { - assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) - .isThrownBy(this.targetAuthorizationManager::doSomething); - } - - @Test - public void targetAuthorizationManagerShouldAllowProtectedMethodInvocationWithCorrectRole() { - UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", - "Password", AuthorityUtils.createAuthorityList("ROLE_USER")); - SecurityContextHolder.getContext().setAuthentication(token); - this.targetAuthorizationManager.doSomething(); - } - - @Test - public void targetAuthorizationManagerShouldPreventProtectedMethodInvocationWithIncorrectRole() { - UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", - "Password", AuthorityUtils.createAuthorityList("ROLE_SOMEOTHERROLE")); - SecurityContextHolder.getContext().setAuthentication(token); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.targetAuthorizationManager::doSomething); - } - - @Test - public void transactionalAuthorizationManagerMethodsShouldBeSecured() { - assertThatExceptionOfType(AuthenticationException.class) - .isThrownBy(this.transactionalTargetAuthorizationManager::doSomething); - } - - @Test - public void targetCustomAuthorizationManagerUsed() { - given(this.mockAuthorizationManager.authorize(any(), any())).willReturn(new AuthorizationDecision(true)); - this.targetCustomAuthorizationManager.doSomething(); - verify(this.mockAuthorizationManager).authorize(any(), any()); - } - - @Override - public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { - this.appContext = applicationContext; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/method/Jsr250AnnotationDrivenBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/method/Jsr250AnnotationDrivenBeanDefinitionParserTests.java deleted file mode 100644 index f13eb7fc1ec..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/Jsr250AnnotationDrivenBeanDefinitionParserTests.java +++ /dev/null @@ -1,100 +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.method; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.annotation.BusinessService; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.ConfigTestUtils; -import org.springframework.security.config.util.InMemoryXmlApplicationContext; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContextHolder; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Luke Taylor - */ -public class Jsr250AnnotationDrivenBeanDefinitionParserTests { - - private InMemoryXmlApplicationContext appContext; - - private BusinessService target; - - @BeforeEach - public void loadContext() { - // @formatter:off - this.appContext = new InMemoryXmlApplicationContext( - "" - + "" - + ConfigTestUtils.AUTH_PROVIDER_XML); - // @formatter:on - this.target = (BusinessService) this.appContext.getBean("target"); - } - - @AfterEach - public void closeAppContext() { - if (this.appContext != null) { - this.appContext.close(); - } - SecurityContextHolder.clearContext(); - } - - @Test - public void targetShouldPreventProtectedMethodInvocationWithNoContext() { - assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) - .isThrownBy(() -> this.target.someUserMethod1()); - } - - @Test - public void permitAllShouldBeDefaultAttribute() { - UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", - "Password", AuthorityUtils.createAuthorityList("ROLE_USER")); - SecurityContextHolder.getContext().setAuthentication(token); - this.target.someOther(0); - } - - @Test - public void targetShouldAllowProtectedMethodInvocationWithCorrectRole() { - UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", - "Password", AuthorityUtils.createAuthorityList("ROLE_USER")); - SecurityContextHolder.getContext().setAuthentication(token); - this.target.someUserMethod1(); - } - - @Test - public void targetShouldPreventProtectedMethodInvocationWithIncorrectRole() { - UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", - "Password", AuthorityUtils.createAuthorityList("ROLE_SOMEOTHERROLE")); - SecurityContextHolder.getContext().setAuthentication(token); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.target::someAdminMethod); - } - - @Test - public void hasAnyRoleAddsDefaultPrefix() { - UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", - "Password", AuthorityUtils.createAuthorityList("ROLE_USER")); - SecurityContextHolder.getContext().setAuthentication(token); - this.target.rolesAllowedUser(); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests.java deleted file mode 100644 index 28aa3272f30..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests.java +++ /dev/null @@ -1,489 +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.method; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.Supplier; - -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.annotation.AnnotationConfigurationException; -import org.springframework.lang.NonNull; -import org.springframework.lang.Nullable; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.PermissionEvaluator; -import org.springframework.security.access.annotation.BusinessService; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.authorization.AuthorizationDecision; -import org.springframework.security.authorization.AuthorizationManager; -import org.springframework.security.authorization.AuthorizationResult; -import org.springframework.security.config.annotation.method.configuration.MethodSecurityService; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.context.SecurityContextHolderStrategy; -import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.test.context.support.WithAnonymousUser; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Mockito.verify; - -/** - * @author Josh Cummings - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class MethodSecurityBeanDefinitionParserTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/method/MethodSecurityBeanDefinitionParserTests"; - - private final UsernamePasswordAuthenticationToken bob = UsernamePasswordAuthenticationToken.unauthenticated("bob", - "bobspassword"); - - @Autowired(required = false) - MethodSecurityService methodSecurityService; - - @Autowired(required = false) - BusinessService businessService; - - public final SpringTestContext spring = new SpringTestContext(this); - - @WithMockUser(roles = "ADMIN") - @Test - public void preAuthorizeWhenRoleAdminThenAccessDeniedException() { - this.spring.configLocations(xml("MethodSecurityService")).autowire(); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorize) - .withMessage("Access Denied"); - } - - @Test - public void configureWhenAspectJThenRegistersAspects() { - this.spring.configLocations(xml("AspectJMethodSecurityServiceEnabled")).autowire(); - assertThat(this.spring.getContext().containsBean("preFilterAspect$0")).isTrue(); - assertThat(this.spring.getContext().containsBean("postFilterAspect$0")).isTrue(); - assertThat(this.spring.getContext().containsBean("preAuthorizeAspect$0")).isTrue(); - assertThat(this.spring.getContext().containsBean("postAuthorizeAspect$0")).isTrue(); - assertThat(this.spring.getContext().containsBean("securedAspect$0")).isTrue(); - assertThat(this.spring.getContext().containsBean("annotationSecurityAspect$0")).isFalse(); - } - - @WithAnonymousUser - @Test - public void preAuthorizePermitAllWhenRoleAnonymousThenPasses() { - this.spring.configLocations(xml("MethodSecurityService")).autowire(); - String result = this.methodSecurityService.preAuthorizePermitAll(); - assertThat(result).isNull(); - } - - @WithAnonymousUser - @Test - public void preAuthorizeNotAnonymousWhenRoleAnonymousThenAccessDeniedException() { - this.spring.configLocations(xml("MethodSecurityService")).autowire(); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(this.methodSecurityService::preAuthorizeNotAnonymous) - .withMessage("Access Denied"); - } - - @WithMockUser - @Test - public void preAuthorizeNotAnonymousWhenRoleUserThenPasses() { - this.spring.configLocations(xml("MethodSecurityService")).autowire(); - this.methodSecurityService.preAuthorizeNotAnonymous(); - } - - @WithMockUser - @Test - public void securedWhenRoleUserThenAccessDeniedException() { - this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::secured) - .withMessage("Access Denied"); - } - - @WithMockUser(roles = "ADMIN") - @Test - public void securedWhenRoleAdminThenPasses() { - this.spring.configLocations(xml("MethodSecurityService")).autowire(); - String result = this.methodSecurityService.secured(); - assertThat(result).isNull(); - } - - @Test - public void securedWhenCustomSecurityContextHolderStrategyThenUses() { - this.spring.configLocations(xml("MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy")).autowire(); - SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); - SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "pass")); - strategy.setContext(context); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::secured) - .withMessage("Access Denied"); - verify(strategy).getContext(); - } - - @WithMockUser(roles = "ADMIN") - @Test - public void securedUserWhenRoleAdminThenAccessDeniedException() { - this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser) - .withMessage("Access Denied"); - } - - @WithMockUser - @Test - public void securedUserWhenRoleUserThenPasses() { - this.spring.configLocations(xml("MethodSecurityService")).autowire(); - String result = this.methodSecurityService.securedUser(); - assertThat(result).isNull(); - } - - @WithMockUser - @Test - public void preAuthorizeAdminWhenRoleUserThenAccessDeniedException() { - this.spring.configLocations(xml("MethodSecurityService")).autowire(); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorizeAdmin) - .withMessage("Access Denied"); - } - - @WithMockUser(roles = "ADMIN") - @Test - public void preAuthorizeAdminWhenRoleAdminThenPasses() { - this.spring.configLocations(xml("MethodSecurityService")).autowire(); - this.methodSecurityService.preAuthorizeAdmin(); - } - - @Test - public void preAuthorizeWhenCustomSecurityContextHolderStrategyThenUses() { - this.spring.configLocations(xml("MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy")).autowire(); - SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); - SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "pass")); - strategy.setContext(context); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorizeAdmin) - .withMessage("Access Denied"); - verify(strategy).getContext(); - } - - @WithMockUser(authorities = "PREFIX_ADMIN") - @Test - public void preAuthorizeAdminWhenRoleAdminAndCustomPrefixThenPasses() { - this.spring.configLocations(xml("CustomGrantedAuthorityDefaults")).autowire(); - this.methodSecurityService.preAuthorizeAdmin(); - } - - @WithMockUser - @Test - public void postHasPermissionWhenParameterIsNotGrantThenAccessDeniedException() { - this.spring.configLocations(xml("CustomPermissionEvaluator")).autowire(); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.methodSecurityService.postHasPermission("deny")) - .withMessage("Access Denied"); - } - - @WithMockUser - @Test - public void postHasPermissionWhenParameterIsGrantThenPasses() { - this.spring.configLocations(xml("CustomPermissionEvaluator")).autowire(); - String result = this.methodSecurityService.postHasPermission("grant"); - assertThat(result).isNull(); - } - - @WithMockUser - @Test - public void postAnnotationWhenParameterIsNotGrantThenAccessDeniedException() { - this.spring.configLocations(xml("MethodSecurityService")).autowire(); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.methodSecurityService.postAnnotation("deny")) - .withMessage("Access Denied"); - } - - @WithMockUser - @Test - public void postAnnotationWhenParameterIsGrantThenPasses() { - this.spring.configLocations(xml("MethodSecurityService")).autowire(); - String result = this.methodSecurityService.postAnnotation("grant"); - assertThat(result).isNull(); - } - - @Test - public void preFilterWhenCustomSecurityContextHolderStrategyThenUses() { - this.spring.configLocations(xml("MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy")).autowire(); - SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); - SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "pass")); - strategy.setContext(context); - List result = this.methodSecurityService - .preFilterByUsername(new ArrayList<>(Arrays.asList("user", "bob", "joe"))); - assertThat(result).containsExactly("user"); - verify(strategy).getContext(); - } - - @Test - public void postFilterWhenCustomSecurityContextHolderStrategyThenUses() { - this.spring.configLocations(xml("MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy")).autowire(); - SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); - SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "pass")); - strategy.setContext(context); - List result = this.methodSecurityService - .postFilterByUsername(new ArrayList<>(Arrays.asList("user", "bob", "joe"))); - assertThat(result).containsExactly("user"); - verify(strategy).getContext(); - } - - @WithMockUser("bob") - @Test - public void methodReturningAListWhenPrePostFiltersConfiguredThenFiltersList() { - this.spring.configLocations(xml("BusinessService")).autowire(); - List names = new ArrayList<>(); - names.add("bob"); - names.add("joe"); - names.add("sam"); - List result = this.businessService.methodReturningAList(names); - assertThat(result).hasSize(1); - assertThat(result.get(0)).isEqualTo("bob"); - } - - @WithMockUser("bob") - @Test - public void methodReturningAnArrayWhenPostFilterConfiguredThenFiltersArray() { - this.spring.configLocations(xml("BusinessService")).autowire(); - List names = new ArrayList<>(); - names.add("bob"); - names.add("joe"); - names.add("sam"); - Object[] result = this.businessService.methodReturningAnArray(names.toArray()); - assertThat(result).hasSize(1); - assertThat(result[0]).isEqualTo("bob"); - } - - @WithMockUser("bob") - @Test - public void securedUserWhenCustomBeforeAdviceConfiguredAndNameBobThenPasses() { - this.spring.configLocations(xml("CustomAuthorizationManagerBeforeAdvice")).autowire(); - String result = this.methodSecurityService.securedUser(); - assertThat(result).isNull(); - } - - @WithMockUser("joe") - @Test - public void securedUserWhenCustomBeforeAdviceConfiguredAndNameNotBobThenAccessDeniedException() { - this.spring.configLocations(xml("CustomAuthorizationManagerBeforeAdvice")).autowire(); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser) - .withMessage("Access Denied"); - } - - @WithMockUser("bob") - @Test - public void securedUserWhenCustomAfterAdviceConfiguredAndNameBobThenGranted() { - this.spring.configLocations(xml("CustomAuthorizationManagerAfterAdvice")).autowire(); - String result = this.methodSecurityService.securedUser(); - assertThat(result).isEqualTo("granted"); - } - - @WithMockUser("joe") - @Test - public void securedUserWhenCustomAfterAdviceConfiguredAndNameNotBobThenAccessDeniedException() { - this.spring.configLocations(xml("CustomAuthorizationManagerAfterAdvice")).autowire(); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::securedUser) - .withMessage("Access Denied for User 'joe'"); - } - - @WithMockUser(roles = "ADMIN") - @Test - public void jsr250WhenRoleAdminThenAccessDeniedException() { - this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::jsr250) - .withMessage("Access Denied"); - } - - @Test - public void jsr250WhenCustomSecurityContextHolderStrategyThenUses() { - this.spring.configLocations(xml("MethodSecurityServiceEnabledCustomSecurityContextHolderStrategy")).autowire(); - SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); - SecurityContext context = new SecurityContextImpl(new TestingAuthenticationToken("user", "pass")); - strategy.setContext(context); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(this.methodSecurityService::jsr250RolesAllowed) - .withMessage("Access Denied"); - verify(strategy).getContext(); - } - - @WithAnonymousUser - @Test - public void jsr250PermitAllWhenRoleAnonymousThenPasses() { - this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); - String result = this.methodSecurityService.jsr250PermitAll(); - assertThat(result).isNull(); - } - - @WithMockUser(roles = "ADMIN") - @Test - public void rolesAllowedUserWhenRoleAdminThenAccessDeniedException() { - this.spring.configLocations(xml("BusinessService")).autowire(); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.businessService::rolesAllowedUser) - .withMessage("Access Denied"); - } - - @WithMockUser - @Test - public void rolesAllowedUserWhenRoleUserThenPasses() { - this.spring.configLocations(xml("BusinessService")).autowire(); - this.businessService.rolesAllowedUser(); - } - - @WithMockUser(roles = { "ADMIN", "USER" }) - @Test - public void manyAnnotationsWhenMeetsConditionsThenReturnsFilteredList() throws Exception { - List names = Arrays.asList("harold", "jonathan", "pete", "bo"); - this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); - List filtered = this.methodSecurityService.manyAnnotations(new ArrayList<>(names)); - assertThat(filtered).hasSize(2); - assertThat(filtered).containsExactly("harold", "jonathan"); - } - - // gh-4003 - // gh-4103 - @WithMockUser - @Test - public void manyAnnotationsWhenUserThenFails() { - List names = Arrays.asList("harold", "jonathan", "pete", "bo"); - this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names))); - } - - @WithMockUser - @Test - public void manyAnnotationsWhenShortListThenFails() { - List names = Arrays.asList("harold", "jonathan", "pete"); - this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names))); - } - - @WithMockUser(roles = "ADMIN") - @Test - public void manyAnnotationsWhenAdminThenFails() { - List names = Arrays.asList("harold", "jonathan", "pete", "bo"); - this.spring.configLocations(xml("MethodSecurityServiceEnabled")).autowire(); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.methodSecurityService.manyAnnotations(new ArrayList<>(names))); - } - - // gh-3183 - @Test - public void repeatedAnnotationsWhenPresentThenFails() { - this.spring.configLocations(xml("MethodSecurityService")).autowire(); - assertThatExceptionOfType(AnnotationConfigurationException.class) - .isThrownBy(() -> this.methodSecurityService.repeatedAnnotations()); - } - - // gh-3183 - @Test - public void repeatedJsr250AnnotationsWhenPresentThenFails() { - this.spring.configLocations(xml("Jsr250")).autowire(); - assertThatExceptionOfType(AnnotationConfigurationException.class) - .isThrownBy(() -> this.businessService.repeatedAnnotations()); - } - - // gh-3183 - @Test - public void repeatedSecuredAnnotationsWhenPresentThenFails() { - this.spring.configLocations(xml("Secured")).autowire(); - assertThatExceptionOfType(AnnotationConfigurationException.class) - .isThrownBy(() -> this.businessService.repeatedAnnotations()); - } - - @WithMockUser - @Test - public void supportsMethodArgumentsInPointcut() { - this.spring.configLocations(xml("ProtectPointcut")).autowire(); - this.businessService.someOther(0); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.businessService.someOther("somestring")); - } - - @Test - public void supportsBooleanPointcutExpressions() { - this.spring.configLocations(xml("ProtectPointcutBoolean")).autowire(); - this.businessService.someOther("somestring"); - // All others should require ROLE_USER - assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) - .isThrownBy(() -> this.businessService.someOther(0)); - SecurityContextHolder.getContext() - .setAuthentication(new TestingAuthenticationToken("user", "password", - AuthorityUtils.createAuthorityList("ROLE_USER"))); - this.businessService.someOther(0); - SecurityContextHolder.clearContext(); - } - - private static String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - - static class MyPermissionEvaluator implements PermissionEvaluator { - - @Override - public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { - return "grant".equals(targetDomainObject); - } - - @Override - public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, - Object permission) { - throw new UnsupportedOperationException(); - } - - } - - static class MyAuthorizationManager implements AuthorizationManager { - - @Override - public AuthorizationResult authorize( - Supplier authentication, - MethodInvocation object) { - return new AuthorizationDecision("bob".equals(authentication.get().getName())); - } - - } - - static class MyAdvice implements MethodInterceptor { - - @Nullable - @Override - public Object invoke(@NonNull MethodInvocation invocation) { - Authentication auth = SecurityContextHolder.getContext().getAuthentication(); - if ("bob".equals(auth.getName())) { - return "granted"; - } - throw new AccessDeniedException("Access Denied for User '" + auth.getName() + "'"); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/method/PreAuthorizeAdminRole.java b/config/src/test/java/org/springframework/security/config/method/PreAuthorizeAdminRole.java deleted file mode 100644 index f6956c1cae8..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/PreAuthorizeAdminRole.java +++ /dev/null @@ -1,32 +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.method; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import org.springframework.security.access.prepost.PreAuthorize; - -/** - * @author Rob Winch - * - */ -@Retention(RetentionPolicy.RUNTIME) -@PreAuthorize("hasRole('ADMIN')") -public @interface PreAuthorizeAdminRole { - -} diff --git a/config/src/test/java/org/springframework/security/config/method/PreAuthorizeServiceImpl.java b/config/src/test/java/org/springframework/security/config/method/PreAuthorizeServiceImpl.java deleted file mode 100644 index 838ff6fa787..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/PreAuthorizeServiceImpl.java +++ /dev/null @@ -1,33 +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.method; - -/** - * @author Rob Winch - * - */ -public class PreAuthorizeServiceImpl { - - @PreAuthorizeAdminRole - public void preAuthorizeAdminRole() { - } - - @ContactPermission - public void contactPermission(Contact contact) { - } - -} diff --git a/config/src/test/java/org/springframework/security/config/method/PreAuthorizeTests.java b/config/src/test/java/org/springframework/security/config/method/PreAuthorizeTests.java deleted file mode 100644 index f3319142f33..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/PreAuthorizeTests.java +++ /dev/null @@ -1,77 +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.method; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Rob Winch - * - */ -@ExtendWith(SpringExtension.class) -@ContextConfiguration -public class PreAuthorizeTests { - - @Autowired - PreAuthorizeServiceImpl service; - - @AfterEach - public void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test - public void preAuthorizeAdminRoleDenied() { - SecurityContextHolder.getContext() - .setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_USER")); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.service::preAuthorizeAdminRole); - } - - @Test - public void preAuthorizeAdminRoleGranted() { - SecurityContextHolder.getContext() - .setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_ADMIN")); - this.service.preAuthorizeAdminRole(); - } - - @Test - public void preAuthorizeContactPermissionGranted() { - SecurityContextHolder.getContext() - .setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_ADMIN")); - this.service.contactPermission(new Contact("user")); - } - - @Test - public void preAuthorizeContactPermissionDenied() { - SecurityContextHolder.getContext() - .setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_ADMIN")); - assertThatExceptionOfType(AccessDeniedException.class) - .isThrownBy(() -> this.service.contactPermission(new Contact("admin"))); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/method/Sec2196Tests.java b/config/src/test/java/org/springframework/security/config/method/Sec2196Tests.java deleted file mode 100644 index 3df45199854..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/Sec2196Tests.java +++ /dev/null @@ -1,85 +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.method; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.util.InMemoryXmlApplicationContext; -import org.springframework.security.core.context.SecurityContextHolder; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Rob Winch - * - */ -public class Sec2196Tests { - - private ConfigurableApplicationContext context; - - @Test - public void genericMethodsProtected() { - loadContext("" - + ""); - SecurityContextHolder.getContext() - .setAuthentication(new TestingAuthenticationToken("test", "pass", "ROLE_USER")); - Service service = this.context.getBean(Service.class); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(() -> service.save(new User())); - } - - @Test - public void genericMethodsAllowed() { - loadContext("" - + ""); - SecurityContextHolder.getContext() - .setAuthentication(new TestingAuthenticationToken("test", "pass", "saveUsers")); - Service service = this.context.getBean(Service.class); - service.save(new User()); - } - - private void loadContext(String context) { - this.context = new InMemoryXmlApplicationContext(context); - } - - @AfterEach - public void closeAppContext() { - if (this.context != null) { - this.context.close(); - this.context = null; - } - SecurityContextHolder.clearContext(); - } - - public static class Service { - - @PreAuthorize("hasAuthority('saveUsers')") - public T save(T dto) { - return dto; - } - - } - - static class User { - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/method/SecuredAdminRole.java b/config/src/test/java/org/springframework/security/config/method/SecuredAdminRole.java deleted file mode 100644 index 44fa10b0eba..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/SecuredAdminRole.java +++ /dev/null @@ -1,32 +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.method; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -import org.springframework.security.access.annotation.Secured; - -/** - * @author Rob Winch - * - */ -@Retention(RetentionPolicy.RUNTIME) -@Secured("ROLE_ADMIN") -public @interface SecuredAdminRole { - -} diff --git a/config/src/test/java/org/springframework/security/config/method/SecuredAnnotationDrivenBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/method/SecuredAnnotationDrivenBeanDefinitionParserTests.java deleted file mode 100644 index 07092b6ec2e..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/SecuredAnnotationDrivenBeanDefinitionParserTests.java +++ /dev/null @@ -1,120 +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.method; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.access.annotation.BusinessService; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.ConfigTestUtils; -import org.springframework.security.config.util.InMemoryXmlApplicationContext; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContextHolder; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Ben Alex - */ -public class SecuredAnnotationDrivenBeanDefinitionParserTests { - - private InMemoryXmlApplicationContext appContext; - - private BusinessService target; - - @BeforeEach - public void loadContext() { - SecurityContextHolder.clearContext(); - this.appContext = new InMemoryXmlApplicationContext( - "" - + "" - + ConfigTestUtils.AUTH_PROVIDER_XML); - this.target = (BusinessService) this.appContext.getBean("target"); - } - - @AfterEach - public void closeAppContext() { - if (this.appContext != null) { - this.appContext.close(); - } - SecurityContextHolder.clearContext(); - } - - @Test - public void targetShouldPreventProtectedMethodInvocationWithNoContext() { - assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) - .isThrownBy(this.target::someUserMethod1); - } - - @Test - public void targetShouldAllowProtectedMethodInvocationWithCorrectRole() { - UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", - "Password", AuthorityUtils.createAuthorityList("ROLE_USER")); - SecurityContextHolder.getContext().setAuthentication(token); - this.target.someUserMethod1(); - } - - @Test - public void targetShouldPreventProtectedMethodInvocationWithIncorrectRole() { - UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken.authenticated("Test", - "Password", AuthorityUtils.createAuthorityList("ROLE_SOMEOTHER")); - SecurityContextHolder.getContext().setAuthentication(token); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.target::someAdminMethod); - } - - // SEC-1387 - @Test - public void targetIsSerializableBeforeUse() throws Exception { - BusinessService chompedTarget = (BusinessService) serializeAndDeserialize(this.target); - assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) - .isThrownBy(chompedTarget::someAdminMethod); - } - - @Test - public void targetIsSerializableAfterUse() throws Exception { - assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class) - .isThrownBy(this.target::someAdminMethod); - SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("u", "p", "ROLE_A")); - BusinessService chompedTarget = (BusinessService) serializeAndDeserialize(this.target); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(chompedTarget::someAdminMethod); - } - - private Object serializeAndDeserialize(Object o) throws IOException, ClassNotFoundException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(baos); - oos.writeObject(o); - oos.flush(); - baos.flush(); - byte[] bytes = baos.toByteArray(); - ByteArrayInputStream is = new ByteArrayInputStream(bytes); - ObjectInputStream ois = new ObjectInputStream(is); - Object o2 = ois.readObject(); - return o2; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/method/SecuredServiceImpl.java b/config/src/test/java/org/springframework/security/config/method/SecuredServiceImpl.java deleted file mode 100644 index ad4afc0e581..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/SecuredServiceImpl.java +++ /dev/null @@ -1,29 +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.method; - -/** - * @author Rob Winch - * - */ -public class SecuredServiceImpl { - - @SecuredAdminRole - public void securedAdminRole() { - } - -} diff --git a/config/src/test/java/org/springframework/security/config/method/SecuredTests.java b/config/src/test/java/org/springframework/security/config/method/SecuredTests.java deleted file mode 100644 index bc299102863..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/SecuredTests.java +++ /dev/null @@ -1,62 +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.method; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.access.AccessDeniedException; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * @author Rob Winch - * - */ -@ExtendWith(SpringExtension.class) -@ContextConfiguration -public class SecuredTests { - - @Autowired - SecuredServiceImpl service; - - @AfterEach - public void cleanup() { - SecurityContextHolder.clearContext(); - } - - @Test - public void securedAdminRoleDenied() { - SecurityContextHolder.getContext() - .setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_USER")); - assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.service::securedAdminRole); - } - - @Test - public void securedAdminRoleGranted() { - SecurityContextHolder.getContext() - .setAuthentication(new TestingAuthenticationToken("user", "pass", "ROLE_ADMIN")); - this.service.securedAdminRole(); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/method/TestPermissionEvaluator.java b/config/src/test/java/org/springframework/security/config/method/TestPermissionEvaluator.java deleted file mode 100644 index 860747aecae..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/TestPermissionEvaluator.java +++ /dev/null @@ -1,37 +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.method; - -import java.io.Serializable; - -import org.springframework.security.access.PermissionEvaluator; -import org.springframework.security.core.Authentication; - -public class TestPermissionEvaluator implements PermissionEvaluator { - - @Override - public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { - return false; - } - - @Override - public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, - Object permission) { - return false; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/method/configuration/Gh4020GlobalMethodSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/method/configuration/Gh4020GlobalMethodSecurityConfigurationTests.java deleted file mode 100644 index 83388555b73..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/configuration/Gh4020GlobalMethodSecurityConfigurationTests.java +++ /dev/null @@ -1,95 +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.method.configuration; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.access.PermissionEvaluator; -import org.springframework.security.access.hierarchicalroles.RoleHierarchy; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; -import org.springframework.security.authentication.AuthenticationTrustResolver; -import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Mockito.mock; - -/** - * @author Rob Winch - */ -@ExtendWith(SpringExtension.class) -@ContextConfiguration -public class Gh4020GlobalMethodSecurityConfigurationTests { - - @Autowired - DenyAllService denyAll; - - // gh-4020 - @Test - public void denyAll() { - assertThatExceptionOfType(AuthenticationCredentialsNotFoundException.class).isThrownBy(this.denyAll::denyAll); - } - - @Configuration - @EnableGlobalMethodSecurity(prePostEnabled = true) - static class SecurityConfig { - - @Bean - PermissionEvaluator permissionEvaluator() { - return mock(PermissionEvaluator.class); - } - - @Bean - RoleHierarchy RoleHierarchy() { - return mock(RoleHierarchy.class); - } - - @Bean - AuthenticationTrustResolver trustResolver() { - return mock(AuthenticationTrustResolver.class); - } - - @Autowired - DenyAllService denyAll; - - } - - @Configuration - static class ServiceConfig { - - @Bean - DenyAllService denyAllService() { - return new DenyAllService(); - } - - } - - @PreAuthorize("denyAll") - static class DenyAllService { - - void denyAll() { - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/method/sec2136/JpaPermissionEvaluator.java b/config/src/test/java/org/springframework/security/config/method/sec2136/JpaPermissionEvaluator.java deleted file mode 100644 index 671e280b59a..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/sec2136/JpaPermissionEvaluator.java +++ /dev/null @@ -1,51 +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.method.sec2136; - -import java.io.Serializable; - -import jakarta.persistence.EntityManager; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.access.PermissionEvaluator; -import org.springframework.security.core.Authentication; - -/** - * @author Rob Winch - * - */ -public class JpaPermissionEvaluator implements PermissionEvaluator { - - @Autowired - private EntityManager entityManager; - - public JpaPermissionEvaluator() { - System.out.println("initializing " + this); - } - - @Override - public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) { - return true; - } - - @Override - public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, - Object permission) { - return true; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/method/sec2136/Sec2136Tests.java b/config/src/test/java/org/springframework/security/config/method/sec2136/Sec2136Tests.java deleted file mode 100644 index 2c41a29b409..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/sec2136/Sec2136Tests.java +++ /dev/null @@ -1,37 +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.method.sec2136; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -/** - * @author Rob Winch - * @since 3.2 - */ -@ExtendWith(SpringExtension.class) -@ContextConfiguration("sec2136.xml") -public class Sec2136Tests { - - @Test - public void configurationLoads() { - } - -} diff --git a/config/src/test/java/org/springframework/security/config/method/sec2499/Sec2499Tests.java b/config/src/test/java/org/springframework/security/config/method/sec2499/Sec2499Tests.java deleted file mode 100644 index 28c7a636586..00000000000 --- a/config/src/test/java/org/springframework/security/config/method/sec2499/Sec2499Tests.java +++ /dev/null @@ -1,53 +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.method.sec2499; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; - -import org.springframework.context.support.GenericXmlApplicationContext; - -/** - * @author Rob Winch - * - */ -public class Sec2499Tests { - - private GenericXmlApplicationContext parent; - - private GenericXmlApplicationContext child; - - @AfterEach - public void cleanup() { - if (this.parent != null) { - this.parent.close(); - } - if (this.child != null) { - this.child.close(); - } - } - - @Test - public void methodExpressionHandlerInParentContextLoads() { - this.parent = new GenericXmlApplicationContext("org/springframework/security/config/method/sec2499/parent.xml"); - this.child = new GenericXmlApplicationContext(); - this.child.load("org/springframework/security/config/method/sec2499/child.xml"); - this.child.setParent(this.parent); - this.child.refresh(); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/oauth2/client/ClientRegistrationsBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/oauth2/client/ClientRegistrationsBeanDefinitionParserTests.java deleted file mode 100644 index 05eb9396d41..00000000000 --- a/config/src/test/java/org/springframework/security/config/oauth2/client/ClientRegistrationsBeanDefinitionParserTests.java +++ /dev/null @@ -1,248 +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.oauth2.client; - -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; -import org.springframework.security.oauth2.core.AuthenticationMethod; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.util.StringUtils; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link ClientRegistrationsBeanDefinitionParser}. - * - * @author Ruby Hartono - * @author Evgeniy Cheban - */ -@ExtendWith(SpringTestContextExtension.class) -public class ClientRegistrationsBeanDefinitionParserTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/oauth2/client/ClientRegistrationsBeanDefinitionParserTests"; - - // @formatter:off - private static final String ISSUER_URI_XML_CONFIG = "\n" - + "\n" - + " \n" - + " \n" - + " \n" - + " \n" - + "\n" - + "\n"; - // @formatter:on - - // @formatter:off - private static final String OIDC_DISCOVERY_RESPONSE = "{\n" - + " \"authorization_endpoint\": \"https://example.com/o/oauth2/v2/auth\", \n" - + " \"claims_supported\": [\n" - + " \"aud\", \n" - + " \"email\", \n" - + " \"email_verified\", \n" - + " \"exp\", \n" - + " \"family_name\", \n" - + " \"given_name\", \n" - + " \"iat\", \n" - + " \"iss\", \n" - + " \"locale\", \n" - + " \"name\", \n" - + " \"picture\", \n" - + " \"sub\"\n" - + " ], \n" - + " \"code_challenge_methods_supported\": [\n" - + " \"plain\", \n" - + " \"S256\"\n" - + " ], \n" - + " \"id_token_signing_alg_values_supported\": [\n" - + " \"RS256\"\n" - + " ], \n" - + " \"issuer\": \"${issuer-uri}\", \n" - + " \"jwks_uri\": \"https://example.com/oauth2/v3/certs\", \n" - + " \"response_types_supported\": [\n" - + " \"code\", \n" - + " \"token\", \n" - + " \"id_token\", \n" - + " \"code token\", \n" - + " \"code id_token\", \n" - + " \"token id_token\", \n" - + " \"code token id_token\", \n" - + " \"none\"\n" - + " ], \n" - + " \"revocation_endpoint\": \"https://example.com/o/oauth2/revoke\", \n" - + " \"scopes_supported\": [\n" - + " \"openid\", \n" - + " \"email\", \n" - + " \"profile\"\n" - + " ], \n" - + " \"subject_types_supported\": [\n" - + " \"public\"\n" - + " ], \n" - + " \"grant_types_supported\" : [\"authorization_code\"], \n" - + " \"token_endpoint\": \"https://example.com/oauth2/v4/token\", \n" - + " \"token_endpoint_auth_methods_supported\": [\n" - + " \"client_secret_post\", \n" - + " \"client_secret_basic\", \n" - + " \"none\"\n" - + " ], \n" - + " \"userinfo_endpoint\": \"https://example.com/oauth2/v3/userinfo\"\n" - + "}"; - // @formatter:on - - @Autowired - private ClientRegistrationRepository clientRegistrationRepository; - - public final SpringTestContext spring = new SpringTestContext(this); - - private MockWebServer server; - - @AfterEach - public void cleanup() throws Exception { - if (this.server != null) { - this.server.shutdown(); - } - } - - @Test - public void parseWhenIssuerUriConfiguredThenRequestConfigFromIssuer() throws Exception { - this.server = new MockWebServer(); - this.server.start(); - String serverUrl = this.server.url("/").toString(); - String discoveryResponse = OIDC_DISCOVERY_RESPONSE.replace("${issuer-uri}", serverUrl); - this.server.enqueue(jsonResponse(discoveryResponse)); - String contextConfig = ISSUER_URI_XML_CONFIG.replace("${issuer-uri}", serverUrl); - this.spring.context(contextConfig).autowire(); - assertThat(this.clientRegistrationRepository).isInstanceOf(InMemoryClientRegistrationRepository.class); - ClientRegistration googleRegistration = this.clientRegistrationRepository.findByRegistrationId("google-login"); - assertThat(googleRegistration).isNotNull(); - assertThat(googleRegistration.getRegistrationId()).isEqualTo("google-login"); - assertThat(googleRegistration.getClientId()).isEqualTo("google-client-id"); - assertThat(googleRegistration.getClientSecret()).isEqualTo("google-client-secret"); - assertThat(googleRegistration.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); - assertThat(googleRegistration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(googleRegistration.getRedirectUri()).isEqualTo("{baseUrl}/{action}/oauth2/code/{registrationId}"); - assertThat(googleRegistration.getScopes()).isNull(); - assertThat(googleRegistration.getClientName()).isEqualTo(serverUrl); - ProviderDetails googleProviderDetails = googleRegistration.getProviderDetails(); - assertThat(googleProviderDetails).isNotNull(); - assertThat(googleProviderDetails.getAuthorizationUri()).isEqualTo("https://example.com/o/oauth2/v2/auth"); - assertThat(googleProviderDetails.getTokenUri()).isEqualTo("https://example.com/oauth2/v4/token"); - assertThat(googleProviderDetails.getUserInfoEndpoint().getUri()) - .isEqualTo("https://example.com/oauth2/v3/userinfo"); - assertThat(googleProviderDetails.getUserInfoEndpoint().getAuthenticationMethod()) - .isEqualTo(AuthenticationMethod.HEADER); - assertThat(googleProviderDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("sub"); - assertThat(googleProviderDetails.getJwkSetUri()).isEqualTo("https://example.com/oauth2/v3/certs"); - assertThat(googleProviderDetails.getIssuerUri()).isEqualTo(serverUrl); - } - - @Test - public void parseWhenMultipleClientsConfiguredThenAvailableInRepository() { - this.spring.configLocations(ClientRegistrationsBeanDefinitionParserTests.xml("MultiClientRegistration")) - .autowire(); - assertThat(this.clientRegistrationRepository).isInstanceOf(InMemoryClientRegistrationRepository.class); - ClientRegistration googleRegistration = this.clientRegistrationRepository.findByRegistrationId("google-login"); - assertThat(googleRegistration).isNotNull(); - assertThat(googleRegistration.getRegistrationId()).isEqualTo("google-login"); - assertThat(googleRegistration.getClientId()).isEqualTo("google-client-id"); - assertThat(googleRegistration.getClientSecret()).isEqualTo("google-client-secret"); - assertThat(googleRegistration.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); - assertThat(googleRegistration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(googleRegistration.getRedirectUri()).isEqualTo("{baseUrl}/login/oauth2/code/{registrationId}"); - assertThat(googleRegistration.getScopes()) - .isEqualTo(StringUtils.commaDelimitedListToSet("openid,profile,email")); - assertThat(googleRegistration.getClientName()).isEqualTo("Google"); - ProviderDetails googleProviderDetails = googleRegistration.getProviderDetails(); - assertThat(googleProviderDetails).isNotNull(); - assertThat(googleProviderDetails.getAuthorizationUri()) - .isEqualTo("https://accounts.google.com/o/oauth2/v2/auth"); - assertThat(googleProviderDetails.getTokenUri()).isEqualTo("https://www.googleapis.com/oauth2/v4/token"); - assertThat(googleProviderDetails.getUserInfoEndpoint().getUri()) - .isEqualTo("https://www.googleapis.com/oauth2/v3/userinfo"); - assertThat(googleProviderDetails.getUserInfoEndpoint().getAuthenticationMethod()) - .isEqualTo(AuthenticationMethod.HEADER); - assertThat(googleProviderDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("sub"); - assertThat(googleProviderDetails.getJwkSetUri()).isEqualTo("https://www.googleapis.com/oauth2/v3/certs"); - assertThat(googleProviderDetails.getIssuerUri()).isEqualTo("https://accounts.google.com"); - ClientRegistration githubRegistration = this.clientRegistrationRepository.findByRegistrationId("github-login"); - assertThat(githubRegistration).isNotNull(); - assertThat(githubRegistration.getRegistrationId()).isEqualTo("github-login"); - assertThat(githubRegistration.getClientId()).isEqualTo("github-client-id"); - assertThat(githubRegistration.getClientSecret()).isEqualTo("github-client-secret"); - assertThat(githubRegistration.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); - assertThat(githubRegistration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(githubRegistration.getRedirectUri()).isEqualTo("{baseUrl}/login/oauth2/code/{registrationId}"); - assertThat(googleRegistration.getScopes()) - .isEqualTo(StringUtils.commaDelimitedListToSet("openid,profile,email")); - assertThat(githubRegistration.getClientName()).isEqualTo("Github"); - ProviderDetails githubProviderDetails = githubRegistration.getProviderDetails(); - assertThat(githubProviderDetails).isNotNull(); - assertThat(githubProviderDetails.getAuthorizationUri()).isEqualTo("https://github.com/login/oauth/authorize"); - assertThat(githubProviderDetails.getTokenUri()).isEqualTo("https://github.com/login/oauth/access_token"); - assertThat(githubProviderDetails.getUserInfoEndpoint().getUri()).isEqualTo("https://api.github.com/user"); - assertThat(githubProviderDetails.getUserInfoEndpoint().getAuthenticationMethod()) - .isEqualTo(AuthenticationMethod.HEADER); - assertThat(githubProviderDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("id"); - } - - @Test - public void parseWhenClientPlaceholdersThenResolvePlaceholders() { - System.setProperty("oauth2.client.id", "github-client-id"); - System.setProperty("oauth2.client.secret", "github-client-secret"); - - this.spring.configLocations(xml("ClientPlaceholders")).autowire(); - - assertThat(this.clientRegistrationRepository).isInstanceOf(InMemoryClientRegistrationRepository.class); - - ClientRegistration githubRegistration = this.clientRegistrationRepository.findByRegistrationId("github"); - assertThat(githubRegistration.getClientId()).isEqualTo("github-client-id"); - assertThat(githubRegistration.getClientSecret()).isEqualTo("github-client-secret"); - } - - private static MockResponse jsonResponse(String json) { - return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE).setBody(json); - } - - private static String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/oauth2/client/CommonOAuth2ProviderTests.java b/config/src/test/java/org/springframework/security/config/oauth2/client/CommonOAuth2ProviderTests.java deleted file mode 100644 index 3a5ad28b3bd..00000000000 --- a/config/src/test/java/org/springframework/security/config/oauth2/client/CommonOAuth2ProviderTests.java +++ /dev/null @@ -1,144 +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.oauth2.client; - -import org.junit.jupiter.api.Test; - -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.ClientAuthenticationMethod; -import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link CommonOAuth2Provider}. - * - * @author Phillip Webb - */ -public class CommonOAuth2ProviderTests { - - private static final String DEFAULT_REDIRECT_URL = "{baseUrl}/{action}/oauth2/code/{registrationId}"; - - @Test - public void getBuilderWhenGoogleShouldHaveGoogleSettings() { - ClientRegistration registration = build(CommonOAuth2Provider.GOOGLE); - ProviderDetails providerDetails = registration.getProviderDetails(); - assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://accounts.google.com/o/oauth2/v2/auth"); - assertThat(providerDetails.getTokenUri()).isEqualTo("https://www.googleapis.com/oauth2/v4/token"); - assertThat(providerDetails.getUserInfoEndpoint().getUri()) - .isEqualTo("https://www.googleapis.com/oauth2/v3/userinfo"); - assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo(IdTokenClaimNames.SUB); - assertThat(providerDetails.getJwkSetUri()).isEqualTo("https://www.googleapis.com/oauth2/v3/certs"); - assertThat(providerDetails.getIssuerUri()).isEqualTo("https://accounts.google.com"); - assertThat(registration.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); - assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(registration.getRedirectUri()).isEqualTo(DEFAULT_REDIRECT_URL); - assertThat(registration.getScopes()).containsOnly("openid", "profile", "email"); - assertThat(registration.getClientName()).isEqualTo("Google"); - assertThat(registration.getRegistrationId()).isEqualTo("123"); - } - - @Test - public void getBuilderWhenGitHubShouldHaveGitHubSettings() { - ClientRegistration registration = build(CommonOAuth2Provider.GITHUB); - ProviderDetails providerDetails = registration.getProviderDetails(); - assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://github.com/login/oauth/authorize"); - assertThat(providerDetails.getTokenUri()).isEqualTo("https://github.com/login/oauth/access_token"); - assertThat(providerDetails.getUserInfoEndpoint().getUri()).isEqualTo("https://api.github.com/user"); - assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("id"); - assertThat(providerDetails.getJwkSetUri()).isNull(); - assertThat(registration.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); - assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(registration.getRedirectUri()).isEqualTo(DEFAULT_REDIRECT_URL); - assertThat(registration.getScopes()).containsOnly("read:user"); - assertThat(registration.getClientName()).isEqualTo("GitHub"); - assertThat(registration.getRegistrationId()).isEqualTo("123"); - } - - @Test - public void getBuilderWhenFacebookShouldHaveFacebookSettings() { - ClientRegistration registration = build(CommonOAuth2Provider.FACEBOOK); - ProviderDetails providerDetails = registration.getProviderDetails(); - assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://www.facebook.com/v2.8/dialog/oauth"); - assertThat(providerDetails.getTokenUri()).isEqualTo("https://graph.facebook.com/v2.8/oauth/access_token"); - assertThat(providerDetails.getUserInfoEndpoint().getUri()) - .isEqualTo("https://graph.facebook.com/me?fields=id,name,email"); - assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("id"); - assertThat(providerDetails.getJwkSetUri()).isNull(); - assertThat(registration.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_POST); - assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(registration.getRedirectUri()).isEqualTo(DEFAULT_REDIRECT_URL); - assertThat(registration.getScopes()).containsOnly("public_profile", "email"); - assertThat(registration.getClientName()).isEqualTo("Facebook"); - assertThat(registration.getRegistrationId()).isEqualTo("123"); - } - - @Test - public void getBuilderWhenOktaShouldHaveOktaSettings() { - ClientRegistration registration = builder(CommonOAuth2Provider.OKTA) - .authorizationUri("https://example.com/auth") - .tokenUri("https://example.com/token") - .userInfoUri("https://example.com/info") - .jwkSetUri("https://example.com/jwkset") - .build(); - ProviderDetails providerDetails = registration.getProviderDetails(); - assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://example.com/auth"); - assertThat(providerDetails.getTokenUri()).isEqualTo("https://example.com/token"); - assertThat(providerDetails.getUserInfoEndpoint().getUri()).isEqualTo("https://example.com/info"); - assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo(IdTokenClaimNames.SUB); - assertThat(providerDetails.getJwkSetUri()).isEqualTo("https://example.com/jwkset"); - assertThat(registration.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); - assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(registration.getRedirectUri()).isEqualTo(DEFAULT_REDIRECT_URL); - assertThat(registration.getScopes()).containsOnly("openid", "profile", "email"); - assertThat(registration.getClientName()).isEqualTo("Okta"); - assertThat(registration.getRegistrationId()).isEqualTo("123"); - } - - @Test - public void getBuilderWhenXShouldHaveXSettings() { - ClientRegistration registration = build(CommonOAuth2Provider.X); - ProviderDetails providerDetails = registration.getProviderDetails(); - assertThat(providerDetails.getAuthorizationUri()).isEqualTo("https://x.com/i/oauth2/authorize"); - assertThat(providerDetails.getTokenUri()).isEqualTo("https://api.x.com/2/oauth2/token"); - assertThat(providerDetails.getUserInfoEndpoint().getUri()).isEqualTo("https://api.x.com/2/users/me"); - assertThat(providerDetails.getUserInfoEndpoint().getUserNameAttributeName()).isEqualTo("username"); - assertThat(providerDetails.getJwkSetUri()).isNull(); - assertThat(registration.getClientAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_POST); - assertThat(registration.getAuthorizationGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(registration.getRedirectUri()).isEqualTo(DEFAULT_REDIRECT_URL); - assertThat(registration.getScopes()).containsOnly("users.read", "tweet.read"); - assertThat(registration.getClientName()).isEqualTo("X"); - assertThat(registration.getRegistrationId()).isEqualTo("123"); - } - - private ClientRegistration build(CommonOAuth2Provider provider) { - return builder(provider).build(); - } - - private ClientRegistration.Builder builder(CommonOAuth2Provider provider) { - return provider.getBuilder("123").clientId("abcd").clientSecret("secret"); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/observation/SecurityObservationSettingsTests.java b/config/src/test/java/org/springframework/security/config/observation/SecurityObservationSettingsTests.java deleted file mode 100644 index eb72ba1926f..00000000000 --- a/config/src/test/java/org/springframework/security/config/observation/SecurityObservationSettingsTests.java +++ /dev/null @@ -1,56 +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.observation; - -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link SecurityObservationSettings} - */ -public class SecurityObservationSettingsTests { - - @Test - void withDefaultsThenFilterOffAuthenticationOnAuthorizationOn() { - SecurityObservationSettings defaults = SecurityObservationSettings.withDefaults().build(); - assertThat(defaults.shouldObserveRequests()).isFalse(); - assertThat(defaults.shouldObserveAuthentications()).isTrue(); - assertThat(defaults.shouldObserveAuthorizations()).isTrue(); - } - - @Test - void noObservationsWhenConstructedThenAllOff() { - SecurityObservationSettings defaults = SecurityObservationSettings.noObservations(); - assertThat(defaults.shouldObserveRequests()).isFalse(); - assertThat(defaults.shouldObserveAuthentications()).isFalse(); - assertThat(defaults.shouldObserveAuthorizations()).isFalse(); - } - - @Test - void withDefaultsWhenExclusionsThenInstanceReflects() { - SecurityObservationSettings defaults = SecurityObservationSettings.withDefaults() - .shouldObserveAuthentications(false) - .shouldObserveAuthorizations(false) - .shouldObserveRequests(true) - .build(); - assertThat(defaults.shouldObserveRequests()).isTrue(); - assertThat(defaults.shouldObserveAuthentications()).isFalse(); - assertThat(defaults.shouldObserveAuthorizations()).isFalse(); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/provisioning/UserDetailsManagerResourceFactoryBeanPropertiesResourceITests.java b/config/src/test/java/org/springframework/security/config/provisioning/UserDetailsManagerResourceFactoryBeanPropertiesResourceITests.java deleted file mode 100644 index 4a6d5e5d6e6..00000000000 --- a/config/src/test/java/org/springframework/security/config/provisioning/UserDetailsManagerResourceFactoryBeanPropertiesResourceITests.java +++ /dev/null @@ -1,56 +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.provisioning; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.provisioning.UserDetailsManager; -import org.springframework.security.util.InMemoryResource; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Rob Winch - * @since 5.0 - */ -@ExtendWith(SpringExtension.class) -public class UserDetailsManagerResourceFactoryBeanPropertiesResourceITests { - - @Autowired - UserDetailsManager users; - - @Test - public void loadUserByUsernameWhenUserFoundThenNotNull() { - assertThat(this.users.loadUserByUsername("user")).isNotNull(); - } - - @Configuration - static class Config { - - @Bean - UserDetailsManagerResourceFactoryBean userDetailsService() { - return UserDetailsManagerResourceFactoryBean.fromResource(new InMemoryResource("user=password,ROLE_USER")); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/provisioning/UserDetailsManagerResourceFactoryBeanPropertiesResourceLocationITests.java b/config/src/test/java/org/springframework/security/config/provisioning/UserDetailsManagerResourceFactoryBeanPropertiesResourceLocationITests.java deleted file mode 100644 index 26ef35c9a96..00000000000 --- a/config/src/test/java/org/springframework/security/config/provisioning/UserDetailsManagerResourceFactoryBeanPropertiesResourceLocationITests.java +++ /dev/null @@ -1,55 +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.provisioning; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.provisioning.UserDetailsManager; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Rob Winch - * @since 5.0 - */ -@ExtendWith(SpringExtension.class) -public class UserDetailsManagerResourceFactoryBeanPropertiesResourceLocationITests { - - @Autowired - UserDetailsManager users; - - @Test - public void loadUserByUsernameWhenUserFoundThenNotNull() { - assertThat(this.users.loadUserByUsername("user")).isNotNull(); - } - - @Configuration - static class Config { - - @Bean - UserDetailsManagerResourceFactoryBean userDetailsService() { - return UserDetailsManagerResourceFactoryBean.fromResourceLocation("classpath:users.properties"); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/provisioning/UserDetailsManagerResourceFactoryBeanStringITests.java b/config/src/test/java/org/springframework/security/config/provisioning/UserDetailsManagerResourceFactoryBeanStringITests.java deleted file mode 100644 index 9abb77c4bf1..00000000000 --- a/config/src/test/java/org/springframework/security/config/provisioning/UserDetailsManagerResourceFactoryBeanStringITests.java +++ /dev/null @@ -1,55 +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.provisioning; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.provisioning.UserDetailsManager; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Rob Winch - * @since 5.0 - */ -@ExtendWith(SpringExtension.class) -public class UserDetailsManagerResourceFactoryBeanStringITests { - - @Autowired - UserDetailsManager users; - - @Test - public void loadUserByUsernameWhenUserFoundThenNotNull() { - assertThat(this.users.loadUserByUsername("user")).isNotNull(); - } - - @Configuration - static class Config { - - @Bean - UserDetailsManagerResourceFactoryBean userDetailsService() { - return UserDetailsManagerResourceFactoryBean.fromString("user=password,ROLE_USER"); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests.java b/config/src/test/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests.java deleted file mode 100644 index 42690a16e8b..00000000000 --- a/config/src/test/java/org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests.java +++ /dev/null @@ -1,323 +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.saml2; - -import jakarta.servlet.http.HttpServletRequest; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.saml2.provider.service.registration.AssertingPartyMetadata; -import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration; -import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; -import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding; -import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations; -import org.springframework.security.saml2.provider.service.web.authentication.OpenSaml5AuthenticationRequestResolver; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.verify; -import static org.springframework.security.web.servlet.TestMockHttpServletRequests.get; - -/** - * Tests for {@link RelyingPartyRegistrationsBeanDefinitionParser}. - * - * @author Marcus da Coregio - */ -@ExtendWith(SpringTestContextExtension.class) -public class RelyingPartyRegistrationsBeanDefinitionParserTests { - - private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/saml2/RelyingPartyRegistrationsBeanDefinitionParserTests"; - - // @formatter:off - private static final String METADATA_LOCATION_XML_CONFIG = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - "\n" + - "\n"; - // @formatter:on - - // @formatter:off - private static final String METADATA_LOCATION_OVERRIDE_PROPERTIES_XML_CONFIG = "\n" + - " \n" + - " \n" + - " " + - " \n" + - "\n" + - "\n"; - // @formatter:on - - // @formatter:off - private static final String METADATA_RESPONSE = "\n" + - "\n" + - " \n" + - " \n" + - " qIGOB+m2Kuq9Vp6F9qs/EFvFzuo6qEGukjICPyVAkjk=NgKak4k9LBAqbi8Za8ALUXW1l4npZ4+MOf8jhmpePDP3msbzjeKkkWFgxx+ILLJYwZzVWd3l028xm2l+SBOwoYRKJ670NgcdSdj6plBTGiZ5NXsXrX5M0zmgvAShREgjth/BKTUct5UVJOTqIxOPwBuCnj+Nn1+QUtY9ekPLrM0O2i+g1wckKaP6D7N+uVBwNgZGoOj5bZ082G7QXRX6Jo0925uKczAIKdIiBbMeKa/0phS2L97AkgQRGi2+j8V66TaDWuDSwd9hA2qzCwjsNui4DVLBwP0/LvgUdcu8g7JBIZ1yTddfByefOTVsU7UuZXkYEn4jU2ouk+u5klSo3Q==\n" + - "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n" + - " \n" + - " \n" + - " \n" + - " John\n" + - " Doe\n" + - " john@doe.com\n" + - " \n" + - "\n"; - // @formatter:on - - @Autowired - @Qualifier("registrations") - private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository; - - public final SpringTestContext spring = new SpringTestContext(this); - - private MockWebServer server; - - @AfterEach - void cleanup() throws Exception { - if (this.server != null) { - this.server.shutdown(); - } - } - - @Test - public void parseWhenMetadataLocationConfiguredThenRequestMetadataFromLocation() throws Exception { - this.server = new MockWebServer(); - this.server.start(); - String serverUrl = this.server.url("/").toString(); - this.server.enqueue(xmlResponse(METADATA_RESPONSE)); - String metadataConfig = METADATA_LOCATION_XML_CONFIG.replace("${metadata-location}", serverUrl); - this.spring.context(metadataConfig).autowire(); - assertThat(this.relyingPartyRegistrationRepository) - .isInstanceOf(InMemoryRelyingPartyRegistrationRepository.class); - RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationRepository - .findByRegistrationId("one"); - AssertingPartyMetadata assertingPartyMetadata = relyingPartyRegistration.getAssertingPartyMetadata(); - assertThat(relyingPartyRegistration).isNotNull(); - assertThat(relyingPartyRegistration.getRegistrationId()).isEqualTo("one"); - assertThat(relyingPartyRegistration.getEntityId()) - .isEqualTo("{baseUrl}/saml2/service-provider-metadata/{registrationId}"); - assertThat(relyingPartyRegistration.getAssertionConsumerServiceLocation()) - .isEqualTo("{baseUrl}/login/saml2/sso/{registrationId}"); - assertThat(relyingPartyRegistration.getAssertionConsumerServiceBinding()).isEqualTo(Saml2MessageBinding.POST); - assertThat(assertingPartyMetadata.getEntityId()) - .isEqualTo("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php"); - assertThat(assertingPartyMetadata.getWantAuthnRequestsSigned()).isFalse(); - assertThat(assertingPartyMetadata.getVerificationX509Credentials()).hasSize(1); - assertThat(assertingPartyMetadata.getEncryptionX509Credentials()).hasSize(1); - assertThat(assertingPartyMetadata.getSingleSignOnServiceLocation()) - .isEqualTo("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php"); - assertThat(assertingPartyMetadata.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); - assertThat(assertingPartyMetadata.getSigningAlgorithms()) - .containsExactly("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"); - } - - @Test - public void parseWhenMetadataLocationConfiguredAndRegistrationHasPropertiesThenDoNotOverrideSpecifiedProperties() - throws Exception { - this.server = new MockWebServer(); - this.server.start(); - String serverUrl = this.server.url("/").toString(); - this.server.enqueue(xmlResponse(METADATA_RESPONSE)); - String metadataConfig = METADATA_LOCATION_OVERRIDE_PROPERTIES_XML_CONFIG.replace("${metadata-location}", - serverUrl); - this.spring.context(metadataConfig).autowire(); - assertThat(this.relyingPartyRegistrationRepository) - .isInstanceOf(InMemoryRelyingPartyRegistrationRepository.class); - RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationRepository - .findByRegistrationId("one"); - AssertingPartyMetadata assertingPartyMetadata = relyingPartyRegistration.getAssertingPartyMetadata(); - assertThat(relyingPartyRegistration).isNotNull(); - assertThat(relyingPartyRegistration.getRegistrationId()).isEqualTo("one"); - assertThat(relyingPartyRegistration.getEntityId()).isEqualTo("https://rp.example.org"); - assertThat(relyingPartyRegistration.getAssertionConsumerServiceLocation()) - .isEqualTo("https://rp.example.org/location"); - assertThat(relyingPartyRegistration.getAssertionConsumerServiceBinding()) - .isEqualTo(Saml2MessageBinding.REDIRECT); - assertThat(assertingPartyMetadata.getEntityId()) - .isEqualTo("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php"); - assertThat(assertingPartyMetadata.getWantAuthnRequestsSigned()).isFalse(); - assertThat(assertingPartyMetadata.getVerificationX509Credentials()).hasSize(1); - assertThat(assertingPartyMetadata.getEncryptionX509Credentials()).hasSize(1); - assertThat(assertingPartyMetadata.getSingleSignOnServiceLocation()) - .isEqualTo("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php"); - assertThat(assertingPartyMetadata.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); - assertThat(assertingPartyMetadata.getSigningAlgorithms()) - .containsExactly("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"); - } - - @Test - public void parseWhenSingleRelyingPartyRegistrationThenAvailableInRepository() { - this.spring.configLocations(xml("SingleRegistration")).autowire(); - assertThat(this.relyingPartyRegistrationRepository) - .isInstanceOf(InMemoryRelyingPartyRegistrationRepository.class); - RelyingPartyRegistration relyingPartyRegistration = this.relyingPartyRegistrationRepository - .findByRegistrationId("one"); - AssertingPartyMetadata assertingPartyMetadata = relyingPartyRegistration.getAssertingPartyMetadata(); - assertThat(relyingPartyRegistration).isNotNull(); - assertThat(relyingPartyRegistration.getRegistrationId()).isEqualTo("one"); - assertThat(relyingPartyRegistration.getEntityId()) - .isEqualTo("{baseUrl}/saml2/service-provider-metadata/{registrationId}"); - assertThat(relyingPartyRegistration.getAssertionConsumerServiceLocation()) - .isEqualTo("{baseUrl}/login/saml2/sso/{registrationId}"); - assertThat(relyingPartyRegistration.getAssertionConsumerServiceBinding()) - .isEqualTo(Saml2MessageBinding.REDIRECT); - assertThat(assertingPartyMetadata.getEntityId()).isEqualTo("https://accounts.google.com/o/saml2/idp/entity-id"); - assertThat(assertingPartyMetadata.getWantAuthnRequestsSigned()).isTrue(); - assertThat(assertingPartyMetadata.getSingleSignOnServiceLocation()) - .isEqualTo("https://accounts.google.com/o/saml2/idp/sso-url"); - assertThat(assertingPartyMetadata.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST); - assertThat(assertingPartyMetadata.getVerificationX509Credentials()).hasSize(1); - assertThat(assertingPartyMetadata.getEncryptionX509Credentials()).hasSize(1); - assertThat(assertingPartyMetadata.getSigningAlgorithms()) - .containsExactly("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"); - } - - @Test - public void parseWhenMultiRelyingPartyRegistrationThenAvailableInRepository() { - this.spring.configLocations(xml("MultiRegistration")).autowire(); - assertThat(this.relyingPartyRegistrationRepository) - .isInstanceOf(InMemoryRelyingPartyRegistrationRepository.class); - RelyingPartyRegistration one = this.relyingPartyRegistrationRepository.findByRegistrationId("one"); - AssertingPartyMetadata google = one.getAssertingPartyMetadata(); - RelyingPartyRegistration two = this.relyingPartyRegistrationRepository.findByRegistrationId("two"); - AssertingPartyMetadata simpleSaml = two.getAssertingPartyMetadata(); - assertThat(one).isNotNull(); - assertThat(one.getRegistrationId()).isEqualTo("one"); - assertThat(one.getEntityId()).isEqualTo("{baseUrl}/saml2/service-provider-metadata/{registrationId}"); - assertThat(one.getAssertionConsumerServiceLocation()).isEqualTo("{baseUrl}/login/saml2/sso/{registrationId}"); - assertThat(one.getAssertionConsumerServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT); - assertThat(google.getEntityId()).isEqualTo("https://accounts.google.com/o/saml2/idp/entity-id"); - assertThat(google.getWantAuthnRequestsSigned()).isTrue(); - assertThat(google.getSingleSignOnServiceLocation()) - .isEqualTo("https://accounts.google.com/o/saml2/idp/sso-url"); - assertThat(google.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST); - assertThat(google.getVerificationX509Credentials()).hasSize(1); - assertThat(google.getEncryptionX509Credentials()).hasSize(1); - assertThat(google.getSigningAlgorithms()).containsExactly("http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"); - assertThat(two).isNotNull(); - assertThat(two.getRegistrationId()).isEqualTo("two"); - assertThat(two.getEntityId()).isEqualTo("{baseUrl}/saml2/service-provider-metadata/{registrationId}"); - assertThat(two.getAssertionConsumerServiceLocation()).isEqualTo("{baseUrl}/login/saml2/sso/{registrationId}"); - assertThat(two.getAssertionConsumerServiceBinding()).isEqualTo(Saml2MessageBinding.POST); - assertThat(simpleSaml.getEntityId()) - .isEqualTo("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/metadata.php"); - assertThat(simpleSaml.getWantAuthnRequestsSigned()).isFalse(); - assertThat(simpleSaml.getSingleSignOnServiceLocation()) - .isEqualTo("https://simplesaml-for-spring-saml.apps.pcfone.io/saml2/idp/SSOService.php"); - assertThat(simpleSaml.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST); - assertThat(simpleSaml.getVerificationX509Credentials()).hasSize(1); - assertThat(simpleSaml.getEncryptionX509Credentials()).hasSize(1); - assertThat(simpleSaml.getSigningAlgorithms()).containsExactly( - "http://www.w3.org/2001/04/xmldsig-more#rsa-sha224", - "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256", - "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"); - } - - @Test - public void parseWhenRelayStateResolverThenUses() { - this.spring.configLocations(xml("RelayStateResolver")).autowire(); - Converter relayStateResolver = this.spring.getContext().getBean(Converter.class); - OpenSaml5AuthenticationRequestResolver authenticationRequestResolver = this.spring.getContext() - .getBean(OpenSaml5AuthenticationRequestResolver.class); - MockHttpServletRequest request = get("/saml2/authenticate/one").build(); - authenticationRequestResolver.resolve(request); - verify(relayStateResolver).convert(request); - } - - @Test - public void parseWhenPlaceholdersThenResolves() throws Exception { - RelyingPartyRegistration sample = TestRelyingPartyRegistrations.relyingPartyRegistration().build(); - System.setProperty("registration-id", sample.getRegistrationId()); - System.setProperty("entity-id", sample.getEntityId()); - System.setProperty("acs-location", sample.getAssertionConsumerServiceLocation()); - System.setProperty("slo-location", sample.getSingleLogoutServiceLocation()); - System.setProperty("slo-response-location", sample.getSingleLogoutServiceResponseLocation()); - try (MockWebServer web = new MockWebServer()) { - web.start(); - String serverUrl = web.url("/metadata").toString(); - web.enqueue(xmlResponse(METADATA_RESPONSE)); - System.setProperty("metadata-location", serverUrl); - this.spring.configLocations(xml("PlaceholderRegistration")).autowire(); - } - RelyingPartyRegistration registration = this.relyingPartyRegistrationRepository - .findByRegistrationId(sample.getRegistrationId()); - assertThat(registration.getRegistrationId()).isEqualTo(sample.getRegistrationId()); - assertThat(registration.getEntityId()).isEqualTo(sample.getEntityId()); - assertThat(registration.getAssertionConsumerServiceLocation()) - .isEqualTo(sample.getAssertionConsumerServiceLocation()); - assertThat(registration.getSingleLogoutServiceLocation()).isEqualTo(sample.getSingleLogoutServiceLocation()); - assertThat(registration.getSingleLogoutServiceResponseLocation()) - .isEqualTo(sample.getSingleLogoutServiceResponseLocation()); - } - - private static MockResponse xmlResponse(String xml) { - return new MockResponse().setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_XML_VALUE).setBody(xml); - } - - private static String xml(String configName) { - return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/test/SpringTestContext.java b/config/src/test/java/org/springframework/security/config/test/SpringTestContext.java deleted file mode 100644 index 2679796bb20..00000000000 --- a/config/src/test/java/org/springframework/security/config/test/SpringTestContext.java +++ /dev/null @@ -1,197 +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.test; - -import java.io.Closeable; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; -import org.springframework.mock.web.MockServletConfig; -import org.springframework.security.config.BeanIds; -import org.springframework.security.config.util.InMemoryXmlWebApplicationContext; -import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers; -import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; -import org.springframework.security.web.servlet.MockServletContext; -import org.springframework.test.context.web.GenericXmlWebContextLoader; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.RequestPostProcessor; -import org.springframework.test.web.servlet.setup.ConfigurableMockMvcBuilder; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.springframework.test.web.servlet.setup.MockMvcConfigurer; -import org.springframework.web.context.ConfigurableWebApplicationContext; -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; -import org.springframework.web.context.support.XmlWebApplicationContext; -import org.springframework.web.filter.OncePerRequestFilter; -import org.springframework.web.server.WebFilter; - -import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; - -/** - * @author Rob Winch - * @since 5.0 - */ -public class SpringTestContext implements Closeable { - - private Object test; - - private ConfigurableWebApplicationContext context; - - private List filters = new ArrayList<>(); - - private DeferAddFilter deferAddFilter = new DeferAddFilter(); - - private List> postProcessors = new ArrayList<>(); - - public SpringTestContext(Object test) { - setTest(test); - } - - public void setTest(Object test) { - this.test = test; - } - - @Override - public void close() { - try { - this.context.close(); - } - catch (Exception ex) { - } - } - - public SpringTestContext context(ConfigurableWebApplicationContext context) { - this.context = context; - return this; - } - - public SpringTestContext register(Class... classes) { - AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); - applicationContext.register(classes); - this.context = applicationContext; - return this; - } - - public SpringTestContext testConfigLocations(String... configLocations) { - GenericXmlWebContextLoader loader = new GenericXmlWebContextLoader(); - String[] locations = loader.processLocations(this.test.getClass(), configLocations); - return configLocations(locations); - } - - public SpringTestContext configLocations(String... configLocations) { - XmlWebApplicationContext context = new XmlWebApplicationContext(); - context.setConfigLocations(configLocations); - this.context = context; - return this; - } - - public SpringTestContext context(String configuration) { - InMemoryXmlWebApplicationContext context = new InMemoryXmlWebApplicationContext(configuration); - this.context = context; - return this; - } - - public SpringTestContext postProcessor(Consumer contextConsumer) { - this.postProcessors.add(contextConsumer); - return this; - } - - public SpringTestContext mockMvcAfterSpringSecurityOk() { - this.deferAddFilter.addFilter(new OncePerRequestFilter() { - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, - FilterChain filterChain) { - response.setStatus(HttpServletResponse.SC_OK); - } - }); - return this; - } - - public SpringTestContext addFilter(Filter filter) { - this.filters.add(filter); - return this; - } - - public ConfigurableWebApplicationContext getContext() { - if (!this.context.isRunning()) { - this.context.setServletContext(MockServletContext.mvc()); - this.context.setServletConfig(new MockServletConfig()); - this.context.refresh(); - } - return this.context; - } - - public void autowire() { - this.context.setServletContext(MockServletContext.mvc()); - this.context.setServletConfig(new MockServletConfig()); - for (Consumer postProcessor : this.postProcessors) { - postProcessor.accept(this.context); - } - this.context.refresh(); - if (this.context.containsBean(BeanIds.SPRING_SECURITY_FILTER_CHAIN)) { - // @formatter:off - MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(this.context) - .addFilters(this.filters.toArray(new Filter[0])) - .apply(springSecurity()) - .apply(this.deferAddFilter) - .build(); - // @formatter:on - this.context.getBeanFactory().registerResolvableDependency(MockMvc.class, mockMvc); - } - String webFluxSecurityBean = "org.springframework.security.config.annotation.web.reactive.WebFluxSecurityConfiguration.WebFilterChainFilter"; - if (this.context.containsBean(webFluxSecurityBean)) { - WebFilter springSecurityFilter = this.context.getBean(webFluxSecurityBean, WebFilter.class); - // @formatter:off - WebTestClient webTest = WebTestClient - .bindToController(new WebTestClientBuilder.Http200RestController()) - .webFilter(springSecurityFilter) - .apply(SecurityMockServerConfigurers.springSecurity()) - .build(); - // @formatter:on - this.context.getBeanFactory().registerResolvableDependency(WebTestClient.class, webTest); - } - AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); - bpp.setBeanFactory(this.context.getBeanFactory()); - bpp.processInjection(this.test); - } - - private static class DeferAddFilter implements MockMvcConfigurer { - - private List filters = new ArrayList<>(); - - void addFilter(Filter filter) { - this.filters.add(filter); - } - - @Override - public RequestPostProcessor beforeMockMvcCreated(ConfigurableMockMvcBuilder builder, - WebApplicationContext context) { - builder.addFilters(this.filters.toArray(new Filter[0])); - return null; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/test/SpringTestContextExtension.java b/config/src/test/java/org/springframework/security/config/test/SpringTestContextExtension.java deleted file mode 100644 index 24196b60d02..00000000000 --- a/config/src/test/java/org/springframework/security/config/test/SpringTestContextExtension.java +++ /dev/null @@ -1,54 +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.test; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; - -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; - -import org.springframework.security.test.context.TestSecurityContextHolder; - -public class SpringTestContextExtension implements BeforeEachCallback, AfterEachCallback { - - @Override - public void afterEach(ExtensionContext context) throws Exception { - TestSecurityContextHolder.clearContext(); - getContexts(context.getRequiredTestInstance()).forEach(SpringTestContext::close); - } - - @Override - public void beforeEach(ExtensionContext context) throws Exception { - Object testInstance = context.getRequiredTestInstance(); - getContexts(testInstance).forEach((springTestContext) -> springTestContext.setTest(testInstance)); - } - - private static List getContexts(Object test) throws IllegalAccessException { - Field[] declaredFields = test.getClass().getDeclaredFields(); - List result = new ArrayList<>(); - for (Field field : declaredFields) { - if (SpringTestContext.class.isAssignableFrom(field.getType())) { - result.add((SpringTestContext) field.get(test)); - } - } - return result; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/test/SpringTestParentApplicationContextExecutionListener.java b/config/src/test/java/org/springframework/security/config/test/SpringTestParentApplicationContextExecutionListener.java deleted file mode 100644 index e803e8295e1..00000000000 --- a/config/src/test/java/org/springframework/security/config/test/SpringTestParentApplicationContextExecutionListener.java +++ /dev/null @@ -1,48 +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.test; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; - -import org.springframework.context.ApplicationContext; -import org.springframework.test.context.TestContext; -import org.springframework.test.context.TestExecutionListener; - -public class SpringTestParentApplicationContextExecutionListener implements TestExecutionListener { - - @Override - public void beforeTestMethod(TestContext testContext) throws Exception { - ApplicationContext parent = testContext.getApplicationContext(); - Object testInstance = testContext.getTestInstance(); - getContexts(testInstance).forEach((springTestContext) -> springTestContext - .postProcessor((applicationContext) -> applicationContext.setParent(parent))); - } - - private static List getContexts(Object test) throws IllegalAccessException { - Field[] declaredFields = test.getClass().getDeclaredFields(); - List result = new ArrayList<>(); - for (Field field : declaredFields) { - if (SpringTestContext.class.isAssignableFrom(field.getType())) { - result.add((SpringTestContext) field.get(test)); - } - } - return result; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/users/AuthenticationTestConfiguration.java b/config/src/test/java/org/springframework/security/config/users/AuthenticationTestConfiguration.java deleted file mode 100644 index 2b01ccdf206..00000000000 --- a/config/src/test/java/org/springframework/security/config/users/AuthenticationTestConfiguration.java +++ /dev/null @@ -1,37 +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.users; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.UserDetailsService; -import org.springframework.security.provisioning.InMemoryUserDetailsManager; - -/** - * @author Rob Winch - * @since 5.0 - */ -@Configuration -public class AuthenticationTestConfiguration { - - @Bean - public static UserDetailsService userDetailsService() { - return new InMemoryUserDetailsManager(PasswordEncodedUser.user(), PasswordEncodedUser.admin()); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/users/ReactiveAuthenticationTestConfiguration.java b/config/src/test/java/org/springframework/security/config/users/ReactiveAuthenticationTestConfiguration.java deleted file mode 100644 index 5cd09d43a60..00000000000 --- a/config/src/test/java/org/springframework/security/config/users/ReactiveAuthenticationTestConfiguration.java +++ /dev/null @@ -1,37 +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.users; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; -import org.springframework.security.core.userdetails.PasswordEncodedUser; -import org.springframework.security.core.userdetails.ReactiveUserDetailsService; - -/** - * @author Rob Winch - * @since 5.0 - */ -@Configuration -public class ReactiveAuthenticationTestConfiguration { - - @Bean - public static ReactiveUserDetailsService userDetailsService() { - return new MapReactiveUserDetailsService(PasswordEncodedUser.user(), PasswordEncodedUser.admin()); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/util/InMemoryXmlApplicationContext.java b/config/src/test/java/org/springframework/security/config/util/InMemoryXmlApplicationContext.java deleted file mode 100644 index 9ef956f9761..00000000000 --- a/config/src/test/java/org/springframework/security/config/util/InMemoryXmlApplicationContext.java +++ /dev/null @@ -1,81 +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.util; - -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.AbstractXmlApplicationContext; -import org.springframework.core.io.Resource; -import org.springframework.security.util.InMemoryResource; - -/** - * @author Luke Taylor - * @author Eddú Meléndez - * @author Emil Sierżęga - */ -public class InMemoryXmlApplicationContext extends AbstractXmlApplicationContext { - - static final String BEANS_OPENING = "\n" + xml + BEANS_CLOSE; - this.inMemoryXml = new InMemoryResource(fullXml); - setAllowBeanDefinitionOverriding(true); - setParent(parent); - refresh(); - } - - @Override - protected DefaultListableBeanFactory createBeanFactory() { - return new DefaultListableBeanFactory(getInternalParentBeanFactory()) { - @Override - protected boolean allowAliasOverriding() { - return true; - } - }; - } - - @Override - protected Resource[] getConfigResources() { - return new Resource[] { this.inMemoryXml }; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/util/InMemoryXmlWebApplicationContext.java b/config/src/test/java/org/springframework/security/config/util/InMemoryXmlWebApplicationContext.java deleted file mode 100644 index 0426c572c43..00000000000 --- a/config/src/test/java/org/springframework/security/config/util/InMemoryXmlWebApplicationContext.java +++ /dev/null @@ -1,56 +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.util; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.support.DefaultListableBeanFactory; -import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; -import org.springframework.context.ApplicationContext; -import org.springframework.core.io.Resource; -import org.springframework.security.util.InMemoryResource; -import org.springframework.web.context.support.AbstractRefreshableWebApplicationContext; - -/** - * @author Joe Grandja - */ -public class InMemoryXmlWebApplicationContext extends AbstractRefreshableWebApplicationContext { - - private Resource inMemoryXml; - - public InMemoryXmlWebApplicationContext(String xml) { - this(xml, InMemoryXmlApplicationContext.SPRING_SECURITY_VERSION, null); - } - - public InMemoryXmlWebApplicationContext(String xml, ApplicationContext parent) { - this(xml, InMemoryXmlApplicationContext.SPRING_SECURITY_VERSION, parent); - } - - public InMemoryXmlWebApplicationContext(String xml, String secVersion, ApplicationContext parent) { - String fullXml = InMemoryXmlApplicationContext.BEANS_OPENING + secVersion + ".xsd'>\n" + xml - + InMemoryXmlApplicationContext.BEANS_CLOSE; - this.inMemoryXml = new InMemoryResource(fullXml); - setAllowBeanDefinitionOverriding(true); - setParent(parent); - } - - @Override - protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException { - XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory); - reader.loadBeanDefinitions(new Resource[] { this.inMemoryXml }); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/util/SpringSecurityVersions.java b/config/src/test/java/org/springframework/security/config/util/SpringSecurityVersions.java deleted file mode 100644 index 7a11527bd00..00000000000 --- a/config/src/test/java/org/springframework/security/config/util/SpringSecurityVersions.java +++ /dev/null @@ -1,57 +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.util; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.springframework.security.core.SpringSecurityCoreVersion; - -/** - * For computing different Spring Security versions - */ -public final class SpringSecurityVersions { - - static final Pattern SCHEMA_VERSION_PATTERN = Pattern.compile("\\d+\\.\\d+(\\.\\d+)?"); - - public static String getCurrentXsdVersionFromSpringSchemas() { - Properties properties = new Properties(); - try (InputStream is = SpringSecurityCoreVersion.class.getClassLoader() - .getResourceAsStream("META-INF/spring.schemas")) { - properties.load(is); - } - catch (IOException ex) { - throw new RuntimeException("Could not read 'META-INF/spring.schemas'", ex); - } - - String inPackageLocation = properties - .getProperty("https://www.springframework.org/schema/security/spring-security.xsd"); - Matcher matcher = SCHEMA_VERSION_PATTERN.matcher(inPackageLocation); - if (matcher.find()) { - return matcher.group(0); - } - throw new IllegalStateException("Failed to find version"); - } - - private SpringSecurityVersions() { - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/web/PathPatternRequestMatcherBuilderFactoryBeanTests.java b/config/src/test/java/org/springframework/security/config/web/PathPatternRequestMatcherBuilderFactoryBeanTests.java deleted file mode 100644 index 7bbc3ccbb8e..00000000000 --- a/config/src/test/java/org/springframework/security/config/web/PathPatternRequestMatcherBuilderFactoryBeanTests.java +++ /dev/null @@ -1,112 +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.web; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.security.web.servlet.TestMockHttpServletRequests; -import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; -import org.springframework.web.util.pattern.PathPatternParser; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -@ExtendWith(MockitoExtension.class) -class PathPatternRequestMatcherBuilderFactoryBeanTests { - - GenericApplicationContext context; - - @BeforeEach - void setUp() { - this.context = new GenericApplicationContext(); - } - - @Test - void getObjectWhenDefaultsThenBuilder() throws Exception { - factoryBean().getObject(); - } - - @Test - void getObjectWhenMvcPatternParserThenUses() throws Exception { - PathPatternParser mvc = registerMvcPatternParser(); - PathPatternRequestMatcher.Builder builder = factoryBean().getObject(); - builder.matcher("/path/**"); - verify(mvc).parse("/path/**"); - } - - @Test - void getObjectWhenPathPatternParserThenUses() throws Exception { - PathPatternParser parser = mock(PathPatternParser.class); - PathPatternRequestMatcher.Builder builder = factoryBean(parser).getObject(); - builder.matcher("/path/**"); - verify(parser).parse("/path/**"); - } - - @Test - void getObjectWhenMvcAndPathPatternParserConflictThenIllegalArgument() { - registerMvcPatternParser(); - PathPatternParser parser = mock(PathPatternParser.class); - assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> factoryBean(parser).getObject()); - } - - @Test - void getObjectWhenMvcAndPathPatternParserAgreeThenUses() throws Exception { - PathPatternParser mvc = registerMvcPatternParser(); - PathPatternRequestMatcher.Builder builder = factoryBean(mvc).getObject(); - builder.matcher("/path/**"); - verify(mvc).parse("/path/**"); - } - - @Test - void getObjectWhenBasePathThenUses() throws Exception { - PathPatternRequestMatcherBuilderFactoryBean factoryBean = new PathPatternRequestMatcherBuilderFactoryBean(); - factoryBean.setApplicationContext(this.context); - factoryBean.setBasePath("/mvc"); - PathPatternRequestMatcher.Builder builder = factoryBean.getObject(); - PathPatternRequestMatcher matcher = builder.matcher("/path/**"); - assertThat(matcher.matches(TestMockHttpServletRequests.get("/mvc/path/123").build())).isTrue(); - assertThat(matcher.matches(TestMockHttpServletRequests.get("/path/123").build())).isFalse(); - } - - PathPatternRequestMatcherBuilderFactoryBean factoryBean() { - PathPatternRequestMatcherBuilderFactoryBean factoryBean = new PathPatternRequestMatcherBuilderFactoryBean(); - factoryBean.setApplicationContext(this.context); - return factoryBean; - } - - PathPatternRequestMatcherBuilderFactoryBean factoryBean(PathPatternParser parser) { - PathPatternRequestMatcherBuilderFactoryBean factoryBean = new PathPatternRequestMatcherBuilderFactoryBean( - parser); - factoryBean.setApplicationContext(this.context); - return factoryBean; - } - - PathPatternParser registerMvcPatternParser() { - PathPatternParser mvc = mock(PathPatternParser.class); - this.context.registerBean(PathPatternRequestMatcherBuilderFactoryBean.MVC_PATTERN_PARSER_BEAN_NAME, - PathPatternParser.class, () -> mvc); - this.context.refresh(); - return mvc; - } - -} diff --git a/config/src/test/java/org/springframework/security/config/web/server/AuthorizeExchangeSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/AuthorizeExchangeSpecTests.java deleted file mode 100644 index b62daaa0a93..00000000000 --- a/config/src/test/java/org/springframework/security/config/web/server/AuthorizeExchangeSpecTests.java +++ /dev/null @@ -1,148 +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.web.server; - -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpMethod; -import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder; -import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; -import org.springframework.test.web.reactive.server.WebTestClient; - -import static org.assertj.core.api.Assertions.assertThatIllegalStateException; - -/** - * @author Rob Winch - * @since 5.0 - */ -public class AuthorizeExchangeSpecTests { - - ServerHttpSecurity http = ServerHttpSecurityConfigurationBuilder.httpWithDefaultAuthentication(); - - @Test - public void antMatchersWhenMethodAndPatternsThenDiscriminatesByMethod() { - this.http.csrf((csrf) -> csrf.disable()) - .authorizeExchange((authorize) -> authorize.pathMatchers(HttpMethod.POST, "/a", "/b") - .denyAll() - .anyExchange() - .permitAll()); - WebTestClient client = buildClient(); - // @formatter:off - client.get() - .uri("/a") - .exchange() - .expectStatus().isOk(); - client.get() - .uri("/b") - .exchange() - .expectStatus().isOk(); - client.post() - .uri("/a") - .exchange() - .expectStatus().isUnauthorized(); - client.post() - .uri("/b") - .exchange() - .expectStatus().isUnauthorized(); - // @formatter:on - } - - @Test - public void antMatchersWhenPatternsThenAnyMethod() { - this.http.csrf((csrf) -> csrf.disable()) - .authorizeExchange((authorize) -> authorize.pathMatchers("/a", "/b").denyAll().anyExchange().permitAll()); - WebTestClient client = buildClient(); - // @formatter:off - client.get() - .uri("/a") - .exchange() - .expectStatus().isUnauthorized(); - client.get() - .uri("/b") - .exchange() - .expectStatus().isUnauthorized(); - client.post() - .uri("/a") - .exchange() - .expectStatus().isUnauthorized(); - client.post() - .uri("/b") - .exchange() - .expectStatus().isUnauthorized(); - // @formatter:on - } - - @Test - public void antMatchersWhenPatternsInLambdaThenAnyMethod() { - this.http.csrf(ServerHttpSecurity.CsrfSpec::disable) - .authorizeExchange((authorize) -> authorize.pathMatchers("/a", "/b").denyAll().anyExchange().permitAll()); - WebTestClient client = buildClient(); - // @formatter:off - client.get() - .uri("/a") - .exchange() - .expectStatus().isUnauthorized(); - client.get() - .uri("/b") - .exchange() - .expectStatus().isUnauthorized(); - client.post() - .uri("/a") - .exchange() - .expectStatus().isUnauthorized(); - client.post() - .uri("/b") - .exchange() - .expectStatus().isUnauthorized(); - // @formatter:on - } - - @Test - public void antMatchersWhenNoAccessAndAnotherMatcherThenThrowsException() { - this.http.authorizeExchange((authorize) -> authorize.pathMatchers("/incomplete")); - assertThatIllegalStateException() - .isThrownBy(() -> this.http.authorizeExchange((authorize) -> authorize.pathMatchers("/throws-exception"))); - } - - @Test - public void anyExchangeWhenFollowedByMatcherThenThrowsException() { - assertThatIllegalStateException().isThrownBy(() -> - // @formatter:off - this.http.authorizeExchange((authorize) -> authorize - .anyExchange().denyAll() - .pathMatchers("/never-reached")) - // @formatter:on - ); - } - - @Test - public void buildWhenMatcherDefinedWithNoAccessThenThrowsException() { - this.http.authorizeExchange((authorize) -> authorize.pathMatchers("/incomplete")); - assertThatIllegalStateException().isThrownBy(this.http::build); - } - - @Test - public void buildWhenMatcherDefinedWithNoAccessInLambdaThenThrowsException() { - this.http.authorizeExchange((authorize) -> authorize.pathMatchers("/incomplete")); - assertThatIllegalStateException().isThrownBy(this.http::build); - } - - private WebTestClient buildClient() { - return WebTestClientBuilder.bindToWebFilters(this.http.build()).build(); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/web/server/CorsSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/CorsSpecTests.java deleted file mode 100644 index 70ca45c7226..00000000000 --- a/config/src/test/java/org/springframework/security/config/web/server/CorsSpecTests.java +++ /dev/null @@ -1,132 +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.web.server; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -import org.springframework.context.ApplicationContext; -import org.springframework.context.support.GenericApplicationContext; -import org.springframework.http.HttpHeaders; -import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; -import org.springframework.test.web.reactive.server.FluxExchangeResult; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.reactive.CorsConfigurationSource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; - -/** - * @author Rob Winch - * @since 5.0 - */ -@ExtendWith(MockitoExtension.class) -public class CorsSpecTests { - - @Mock - private CorsConfigurationSource source; - - private ApplicationContext context; - - ServerHttpSecurity http; - - HttpHeaders expectedHeaders = new HttpHeaders(); - - Set headerNamesNotPresent = new HashSet<>(); - - @BeforeEach - public void setup() { - this.context = new GenericApplicationContext(); - ((GenericApplicationContext) this.context).refresh(); - this.http = new TestingServerHttpSecurity().applicationContext(this.context); - } - - private void givenGetCorsConfigurationWillReturnWildcard() { - CorsConfiguration value = new CorsConfiguration(); - value.setAllowedOrigins(Arrays.asList("*")); - given(this.source.getCorsConfiguration(any())).willReturn(value); - } - - @Test - public void corsWhenEnabledThenAccessControlAllowOriginAndSecurityHeaders() { - givenGetCorsConfigurationWillReturnWildcard(); - this.http.cors((cors) -> cors.configurationSource(this.source)); - this.expectedHeaders.set("Access-Control-Allow-Origin", "*"); - this.expectedHeaders.set("X-Frame-Options", "DENY"); - assertHeaders(); - } - - @Test - public void corsWhenEnabledInLambdaThenAccessControlAllowOriginAndSecurityHeaders() { - givenGetCorsConfigurationWillReturnWildcard(); - this.http.cors((cors) -> cors.configurationSource(this.source)); - this.expectedHeaders.set("Access-Control-Allow-Origin", "*"); - this.expectedHeaders.set("X-Frame-Options", "DENY"); - assertHeaders(); - } - - @Test - public void corsWhenCorsConfigurationSourceBeanThenAccessControlAllowOriginAndSecurityHeaders() { - givenGetCorsConfigurationWillReturnWildcard(); - ((GenericApplicationContext) this.context).registerBean(CorsConfigurationSource.class, () -> this.source); - this.expectedHeaders.set("Access-Control-Allow-Origin", "*"); - this.expectedHeaders.set("X-Frame-Options", "DENY"); - assertHeaders(); - } - - @Test - public void corsWhenNoConfigurationSourceThenNoCorsHeaders() { - this.headerNamesNotPresent.add("Access-Control-Allow-Origin"); - assertHeaders(); - } - - private void assertHeaders() { - WebTestClient client = buildClient(); - // @formatter:off - FluxExchangeResult response = client.get() - .uri("https://example.com/") - .headers((h) -> h.setOrigin("https://origin.example.com")) - .exchange() - .returnResult(String.class); - // @formatter:on - HttpHeaders responseHeaders = response.getResponseHeaders(); - if (!this.expectedHeaders.isEmpty()) { - this.expectedHeaders.forEach( - (headerName, headerValues) -> assertThat(responseHeaders.get(headerName)).isEqualTo(headerValues)); - } - if (!this.headerNamesNotPresent.isEmpty()) { - assertThat(responseHeaders.headerNames()).doesNotContainAnyElementsOf(this.headerNamesNotPresent); - } - } - - private WebTestClient buildClient() { - // @formatter:off - return WebTestClientBuilder.bindToWebFilters(this.http.build()) - .build(); - // @formatter:on - } - -} diff --git a/config/src/test/java/org/springframework/security/config/web/server/ExceptionHandlingSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/ExceptionHandlingSpecTests.java deleted file mode 100644 index 529201d0242..00000000000 --- a/config/src/test/java/org/springframework/security/config/web/server/ExceptionHandlingSpecTests.java +++ /dev/null @@ -1,220 +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.web.server; - -import org.junit.jupiter.api.Test; - -import org.springframework.http.HttpStatus; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder; -import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.security.web.server.ServerAuthenticationEntryPoint; -import org.springframework.security.web.server.authentication.RedirectServerAuthenticationEntryPoint; -import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler; -import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler; -import org.springframework.test.web.reactive.server.WebTestClient; - -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * @author Denys Ivano - * @since 5.0.5 - */ -public class ExceptionHandlingSpecTests { - - private ServerHttpSecurity http = ServerHttpSecurityConfigurationBuilder.httpWithDefaultAuthentication(); - - @Test - public void defaultAuthenticationEntryPoint() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .csrf((csrf) -> csrf.disable()) - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated()) - .exceptionHandling(withDefaults()) - .build(); - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - client.get() - .uri("/test") - .exchange() - .expectStatus().isUnauthorized() - .expectHeader().valueMatches("WWW-Authenticate", "Basic.*"); - // @formatter:on - } - - @Test - public void requestWhenExceptionHandlingWithDefaultsInLambdaThenDefaultAuthenticationEntryPointUsed() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated() - ) - .exceptionHandling(withDefaults()) - .build(); - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - client.get() - .uri("/test") - .exchange() - .expectStatus().isUnauthorized() - .expectHeader().valueMatches("WWW-Authenticate", "Basic.*"); - // @formatter:on - } - - @Test - public void customAuthenticationEntryPoint() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .csrf((csrf) -> csrf.disable()) - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated()) - .exceptionHandling((handling) -> handling - .authenticationEntryPoint(redirectServerAuthenticationEntryPoint("/auth"))) - .build(); - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - client.get() - .uri("/test") - .exchange() - .expectStatus().isFound() - .expectHeader().valueMatches("Location", ".*"); - // @formatter:on - } - - @Test - public void requestWhenCustomAuthenticationEntryPointInLambdaThenCustomAuthenticationEntryPointUsed() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated() - ) - .exceptionHandling((exceptionHandling) -> exceptionHandling - .authenticationEntryPoint(redirectServerAuthenticationEntryPoint("/auth")) - ) - .build(); - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - client.get() - .uri("/test") - .exchange() - .expectStatus().isFound() - .expectHeader().valueMatches("Location", ".*"); - // @formatter:on - } - - @Test - public void defaultAccessDeniedHandler() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .csrf((csrf) -> csrf.disable()) - .httpBasic(Customizer.withDefaults()) - .authorizeExchange((authorize) -> authorize - .anyExchange().hasRole("ADMIN")) - .exceptionHandling(withDefaults()) - .build(); - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - client.get() - .uri("/admin") - .headers((headers) -> headers.setBasicAuth("user", "password")) - .exchange() - .expectStatus().isForbidden(); - // @formatter:on - } - - @Test - public void requestWhenExceptionHandlingWithDefaultsInLambdaThenDefaultAccessDeniedHandlerUsed() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .httpBasic(withDefaults()) - .authorizeExchange((authorize) -> authorize - .anyExchange().hasRole("ADMIN") - ) - .exceptionHandling(withDefaults()) - .build(); - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - client.get() - .uri("/admin") - .headers((headers) -> headers.setBasicAuth("user", "password")) - .exchange() - .expectStatus().isForbidden(); - // @formatter:on - } - - @Test - public void customAccessDeniedHandler() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .csrf((csrf) -> csrf.disable()) - .httpBasic(Customizer.withDefaults()) - .authorizeExchange((authorize) -> authorize - .anyExchange().hasRole("ADMIN")) - .exceptionHandling((handling) -> handling - .accessDeniedHandler(httpStatusServerAccessDeniedHandler(HttpStatus.BAD_REQUEST))) - .build(); - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - client.get() - .uri("/admin") - .headers((headers) -> headers.setBasicAuth("user", "password")) - .exchange() - .expectStatus().isBadRequest(); - // @formatter:on - } - - @Test - public void requestWhenCustomAccessDeniedHandlerInLambdaThenCustomAccessDeniedHandlerUsed() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .httpBasic(withDefaults()) - .authorizeExchange((authorize) -> authorize - .anyExchange().hasRole("ADMIN") - ) - .exceptionHandling((exceptionHandling) -> exceptionHandling - .accessDeniedHandler(httpStatusServerAccessDeniedHandler(HttpStatus.BAD_REQUEST)) - ) - .build(); - WebTestClient client = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - client.get() - .uri("/admin") - .headers((headers) -> headers.setBasicAuth("user", "password")) - .exchange() - .expectStatus().isBadRequest(); - // @formatter:on - } - - private ServerAuthenticationEntryPoint redirectServerAuthenticationEntryPoint(String location) { - return new RedirectServerAuthenticationEntryPoint(location); - } - - private ServerAccessDeniedHandler httpStatusServerAccessDeniedHandler(HttpStatus httpStatus) { - return new HttpStatusServerAccessDeniedHandler(httpStatus); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/web/server/FormLoginTests.java b/config/src/test/java/org/springframework/security/config/web/server/FormLoginTests.java deleted file mode 100644 index d6ec96cb731..00000000000 --- a/config/src/test/java/org/springframework/security/config/web/server/FormLoginTests.java +++ /dev/null @@ -1,587 +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.web.server; - -import org.junit.jupiter.api.Test; -import org.openqa.selenium.By; -import org.openqa.selenium.NoSuchElementException; -import org.openqa.selenium.WebDriver; -import org.openqa.selenium.WebElement; -import org.openqa.selenium.support.FindBy; -import org.openqa.selenium.support.PageFactory; -import reactor.core.publisher.Mono; - -import org.springframework.security.authentication.ReactiveAuthenticationManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder; -import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.security.web.server.WebFilterChainProxy; -import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler; -import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler; -import org.springframework.security.web.server.context.ServerSecurityContextRepository; -import org.springframework.security.web.server.csrf.CsrfToken; -import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher; -import org.springframework.stereotype.Controller; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * @author Rob Winch - * @author Eddú Meléndez - * @since 5.0 - */ -public class FormLoginTests { - - private ServerHttpSecurity http = ServerHttpSecurityConfigurationBuilder.httpWithDefaultAuthentication(); - - @Test - public void defaultLoginPage() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated()) - .formLogin(withDefaults()) - .build(); - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - // @formatter:on - DefaultLoginPage loginPage = HomePage.to(driver, DefaultLoginPage.class).assertAt(); - // @formatter:off - loginPage = loginPage.loginForm() - .username("user") - .password("invalid") - .submit(DefaultLoginPage.class) - .assertError(); - HomePage homePage = loginPage.loginForm() - .username("user") - .password("password") - .submit(HomePage.class); - // @formatter:on - homePage.assertAt(); - loginPage = DefaultLogoutPage.to(driver).assertAt().logout(); - loginPage.assertAt().assertLogout(); - } - - @Test - public void formLoginWhenDefaultsInLambdaThenCreatesDefaultLoginPage() { - SecurityWebFilterChain securityWebFilter = this.http - .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) - .formLogin(withDefaults()) - .build(); - WebTestClient webTestClient = WebTestClientBuilder.bindToWebFilters(securityWebFilter).build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder.webTestClientSetup(webTestClient).build(); - DefaultLoginPage loginPage = HomePage.to(driver, DefaultLoginPage.class).assertAt(); - // @formatter:off - loginPage = loginPage - .loginForm() - .username("user") - .password("invalid") - .submit(DefaultLoginPage.class) - .assertError(); - HomePage homePage = loginPage.loginForm() - .username("user") - .password("password") - .submit(HomePage.class); - // @formatter:on - homePage.assertAt(); - loginPage = DefaultLogoutPage.to(driver).assertAt().logout(); - loginPage.assertAt().assertLogout(); - } - - @Test - public void customLoginPage() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .authorizeExchange((authorize) -> authorize - .pathMatchers("/login").permitAll() - .anyExchange().authenticated()) - .formLogin((login) -> login - .loginPage("/login")) - .build(); - WebTestClient webTestClient = WebTestClient - .bindToController(new CustomLoginPageController(), new WebTestClientBuilder.Http200RestController()) - .webFilter(new WebFilterChainProxy(securityWebFilter)) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - // @formatter:on - CustomLoginPage loginPage = HomePage.to(driver, CustomLoginPage.class).assertAt(); - // @formatter:off - HomePage homePage = loginPage.loginForm() - .username("user") - .password("password") - .submit(HomePage.class); - // @formatter:on - homePage.assertAt(); - } - - @Test - public void formLoginWhenCustomLoginPageInLambdaThenUsed() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .authorizeExchange((authorize) -> authorize - .pathMatchers("/login").permitAll() - .anyExchange().authenticated() - ) - .formLogin((formLogin) -> formLogin - .loginPage("/login") - ) - .build(); - WebTestClient webTestClient = WebTestClient - .bindToController(new CustomLoginPageController(), new WebTestClientBuilder.Http200RestController()) - .webFilter(new WebFilterChainProxy(securityWebFilter)) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - // @formatter:on - CustomLoginPage loginPage = HomePage.to(driver, CustomLoginPage.class).assertAt(); - // @formatter:off - HomePage homePage = loginPage.loginForm() - .username("user") - .password("password") - .submit(HomePage.class); - // @formatter:on - homePage.assertAt(); - } - - @Test - public void formLoginWhenCustomAuthenticationFailureHandlerThenUsed() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .authorizeExchange((authorize) -> authorize - .pathMatchers("/login", "/failure").permitAll() - .anyExchange().authenticated()) - .formLogin((login) -> login - .authenticationFailureHandler(new RedirectServerAuthenticationFailureHandler("/failure"))) - .build(); - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - // @formatter:on - DefaultLoginPage loginPage = HomePage.to(driver, DefaultLoginPage.class).assertAt(); - // @formatter:off - loginPage.loginForm() - .username("invalid") - .password("invalid") - .submit(HomePage.class); - // @formatter:on - assertThat(driver.getCurrentUrl()).endsWith("/failure"); - } - - @Test - public void formLoginWhenCustomRequiresAuthenticationMatcherThenUsed() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .authorizeExchange((authorize) -> authorize - .pathMatchers("/login", "/sign-in").permitAll() - .anyExchange().authenticated()) - .formLogin((login) -> login - .requiresAuthenticationMatcher(new PathPatternParserServerWebExchangeMatcher("/sign-in"))) - .build(); - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - // @formatter:on - driver.get("http://localhost/sign-in"); - assertThat(driver.getCurrentUrl()).endsWith("/login?error"); - } - - @Test - public void authenticationSuccess() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated()) - .formLogin((login) -> login - .authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/custom"))) - .build(); - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - // @formatter:on - DefaultLoginPage loginPage = DefaultLoginPage.to(driver).assertAt(); - // @formatter:off - HomePage homePage = loginPage.loginForm() - .username("user") - .password("password") - .submit(HomePage.class); - // @formatter:on - assertThat(driver.getCurrentUrl()).endsWith("/custom"); - } - - @Test - public void customAuthenticationManager() { - ReactiveAuthenticationManager defaultAuthenticationManager = mock(ReactiveAuthenticationManager.class); - ReactiveAuthenticationManager customAuthenticationManager = mock(ReactiveAuthenticationManager.class); - given(defaultAuthenticationManager.authenticate(any())) - .willThrow(new RuntimeException("should not interact with default auth manager")); - given(customAuthenticationManager.authenticate(any())) - .willReturn(Mono.just(new TestingAuthenticationToken("user", "password", "ROLE_USER", "ROLE_ADMIN"))); - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .authenticationManager(defaultAuthenticationManager) - .formLogin((login) -> login - .authenticationManager(customAuthenticationManager)) - .build(); - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - // @formatter:on - DefaultLoginPage loginPage = DefaultLoginPage.to(driver).assertAt(); - // @formatter:off - HomePage homePage = loginPage.loginForm() - .username("user") - .password("password") - .submit(HomePage.class); - // @formatter:on - homePage.assertAt(); - verifyNoMoreInteractions(defaultAuthenticationManager); - } - - @Test - public void formLoginSecurityContextRepository() { - ServerSecurityContextRepository defaultSecContextRepository = mock(ServerSecurityContextRepository.class); - ServerSecurityContextRepository formLoginSecContextRepository = mock(ServerSecurityContextRepository.class); - TestingAuthenticationToken token = new TestingAuthenticationToken("rob", "rob", "ROLE_USER"); - given(defaultSecContextRepository.save(any(), any())).willReturn(Mono.empty()); - given(defaultSecContextRepository.load(any())).willReturn(authentication(token)); - given(formLoginSecContextRepository.save(any(), any())).willReturn(Mono.empty()); - given(formLoginSecContextRepository.load(any())).willReturn(authentication(token)); - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated()) - .securityContextRepository(defaultSecContextRepository) - .formLogin((login) -> login - .securityContextRepository(formLoginSecContextRepository)) - .build(); - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - // @formatter:on - DefaultLoginPage loginPage = DefaultLoginPage.to(driver).assertAt(); - // @formatter:off - HomePage homePage = loginPage.loginForm() - .username("user") - .password("password") - .submit(HomePage.class); - // @formatter:on - homePage.assertAt(); - verify(defaultSecContextRepository, atLeastOnce()).load(any()); - verify(formLoginSecContextRepository).save(any(), any()); - } - - Mono authentication(Authentication authentication) { - SecurityContext context = new SecurityContextImpl(); - context.setAuthentication(authentication); - return Mono.just(context); - } - - public static class CustomLoginPage { - - private WebDriver driver; - - private LoginForm loginForm; - - public CustomLoginPage(WebDriver webDriver) { - this.driver = webDriver; - this.loginForm = PageFactory.initElements(webDriver, LoginForm.class); - } - - public CustomLoginPage assertAt() { - assertThat(this.driver.getTitle()).isEqualTo("Custom Log In Page"); - return this; - } - - public LoginForm loginForm() { - return this.loginForm; - } - - public static class LoginForm { - - private WebDriver driver; - - private WebElement username; - - private WebElement password; - - @FindBy(css = "button[type=submit]") - private WebElement submit; - - public LoginForm(WebDriver driver) { - this.driver = driver; - } - - public LoginForm username(String username) { - this.username.sendKeys(username); - return this; - } - - public LoginForm password(String password) { - this.password.sendKeys(password); - return this; - } - - public T submit(Class page) { - this.submit.click(); - return PageFactory.initElements(this.driver, page); - } - - } - - } - - public static class DefaultLoginPage { - - private WebDriver driver; - - @FindBy(css = "div[role=alert]") - private WebElement alert; - - private LoginForm loginForm; - - private OAuth2Login oauth2Login = new OAuth2Login(); - - public DefaultLoginPage(WebDriver webDriver) { - this.driver = webDriver; - } - - static DefaultLoginPage create(WebDriver driver) { - return PageFactory.initElements(driver, DefaultLoginPage.class); - } - - public DefaultLoginPage assertAt() { - assertThat(this.driver.getTitle()).isEqualTo("Please sign in"); - return this; - } - - public DefaultLoginPage assertError() { - assertThat(this.alert.getText()).isEqualTo("Invalid credentials"); - return this; - } - - public DefaultLoginPage assertLogout() { - assertThat(this.alert.getText()).isEqualTo("You have been signed out"); - return this; - } - - public DefaultLoginPage assertLoginFormNotPresent() { - assertThatExceptionOfType(NoSuchElementException.class).isThrownBy(() -> loginForm().username("")); - return this; - } - - public DefaultLoginPage assertLoginFormPresent() { - loginForm().username(""); - return this; - } - - public LoginForm loginForm() { - if (this.loginForm == null) { - this.loginForm = PageFactory.initElements(this.driver, LoginForm.class); - } - return this.loginForm; - } - - public OAuth2Login oauth2Login() { - return this.oauth2Login; - } - - static DefaultLoginPage to(WebDriver driver) { - driver.get("http://localhost/login"); - return PageFactory.initElements(driver, DefaultLoginPage.class); - } - - public static class LoginForm { - - private WebDriver driver; - - private WebElement username; - - private WebElement password; - - @FindBy(css = "button[type=submit]") - private WebElement submit; - - public LoginForm(WebDriver driver) { - this.driver = driver; - } - - public LoginForm username(String username) { - this.username.sendKeys(username); - return this; - } - - public LoginForm password(String password) { - this.password.sendKeys(password); - return this; - } - - public T submit(Class page) { - this.submit.click(); - return PageFactory.initElements(this.driver, page); - } - - } - - public class OAuth2Login { - - public WebElement findClientRegistrationByName(String clientName) { - return DefaultLoginPage.this.driver.findElement(By.linkText(clientName)); - } - - public OAuth2Login assertClientRegistrationByName(String clientName) { - findClientRegistrationByName(clientName); - return this; - } - - public DefaultLoginPage and() { - return DefaultLoginPage.this; - } - - } - - } - - public static class DefaultLogoutPage { - - private WebDriver driver; - - @FindBy(css = "button[type=submit]") - private WebElement submit; - - public DefaultLogoutPage(WebDriver webDriver) { - this.driver = webDriver; - } - - public DefaultLogoutPage assertAt() { - assertThat(this.driver.getTitle()).isEqualTo("Confirm Log Out?"); - return this; - } - - public DefaultLoginPage logout() { - this.submit.click(); - return DefaultLoginPage.create(this.driver); - } - - static DefaultLogoutPage to(WebDriver driver) { - driver.get("http://localhost/logout"); - return PageFactory.initElements(driver, DefaultLogoutPage.class); - } - - } - - public static class HomePage { - - private WebDriver driver; - - @FindBy(tagName = "body") - WebElement body; - - public HomePage(WebDriver driver) { - this.driver = driver; - } - - public void assertAt() { - assertThat(this.body.getText()).isEqualToIgnoringWhitespace("ok"); - } - - static T to(WebDriver driver, Class page) { - driver.get("http://localhost/"); - return PageFactory.initElements(driver, page); - } - - } - - @Controller - public static class CustomLoginPageController { - - @ResponseBody - @GetMapping("/login") - public Mono login(ServerWebExchange exchange) { - Mono token = exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty()); - // @formatter:off - return token.map((t) -> "\n" - + "\n" - + " \n" - + " \n" - + " \n" - + " \n" - + " \n" - + " Custom Log In Page\n" - + " \n" - + " \n" - + "
\n" - + "
\n" - + "

Please sign in

\n" - + "

\n" - + " \n" - + " \n" - + "

\n" - + "

\n" - + " \n" - + " \n" - + "

\n" - + " \n" - + " \n" - + "
\n" - + "
\n" - + " \n" - + ""); - // @formatter:on - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java deleted file mode 100644 index 7fe1052f323..00000000000 --- a/config/src/test/java/org/springframework/security/config/web/server/HeaderSpecTests.java +++ /dev/null @@ -1,528 +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.web.server; - -import java.time.Duration; -import java.util.HashSet; -import java.util.Set; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; - -import org.springframework.http.HttpHeaders; -import org.springframework.security.config.Customizer; -import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; -import org.springframework.security.web.server.header.ContentSecurityPolicyServerHttpHeadersWriter; -import org.springframework.security.web.server.header.ContentTypeOptionsServerHttpHeadersWriter; -import org.springframework.security.web.server.header.CrossOriginEmbedderPolicyServerHttpHeadersWriter; -import org.springframework.security.web.server.header.CrossOriginOpenerPolicyServerHttpHeadersWriter; -import org.springframework.security.web.server.header.CrossOriginResourcePolicyServerHttpHeadersWriter; -import org.springframework.security.web.server.header.FeaturePolicyServerHttpHeadersWriter; -import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter; -import org.springframework.security.web.server.header.ReferrerPolicyServerHttpHeadersWriter.ReferrerPolicy; -import org.springframework.security.web.server.header.StrictTransportSecurityServerHttpHeadersWriter; -import org.springframework.security.web.server.header.XFrameOptionsServerHttpHeadersWriter; -import org.springframework.security.web.server.header.XXssProtectionServerHttpHeadersWriter; -import org.springframework.test.web.reactive.server.FluxExchangeResult; -import org.springframework.test.web.reactive.server.WebTestClient; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * Tests for {@link ServerHttpSecurity.HeaderSpec}. - * - * @author Rob Winch - * @author Vedran Pavic - * @author Ankur Pathak - * @author Marcus Da Coregio - * @since 5.0 - */ -public class HeaderSpecTests { - - private static final String CUSTOM_HEADER = "CUSTOM-HEADER"; - - private static final String CUSTOM_VALUE = "CUSTOM-VALUE"; - - private ServerHttpSecurity http = ServerHttpSecurity.http(); - - private HttpHeaders expectedHeaders = new HttpHeaders(); - - private Set headerNamesNotPresent = new HashSet<>(); - - @BeforeEach - public void setup() { - this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, - "max-age=31536000 ; includeSubDomains"); - this.expectedHeaders.add(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, max-age=0, must-revalidate"); - this.expectedHeaders.add(HttpHeaders.PRAGMA, "no-cache"); - this.expectedHeaders.add(HttpHeaders.EXPIRES, "0"); - this.expectedHeaders.add(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS, "nosniff"); - this.expectedHeaders.add(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "DENY"); - this.expectedHeaders.add(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0"); - } - - @Test - public void headersWhenDisableThenNoSecurityHeaders() { - new HashSet<>(this.expectedHeaders.headerNames()).forEach(this::expectHeaderNamesNotPresent); - this.http.headers((headers) -> headers.disable()); - assertHeaders(); - } - - @Test - public void headersWhenDisableInLambdaThenNoSecurityHeaders() { - new HashSet<>(this.expectedHeaders.headerNames()).forEach(this::expectHeaderNamesNotPresent); - this.http.headers((headers) -> headers.disable()); - assertHeaders(); - } - - @Test - public void headersWhenDisableAndInvokedExplicitlyThenDefautsUsed() { - this.http.headers((headers) -> headers.disable().headers(Customizer.withDefaults())); - assertHeaders(); - } - - @Test - public void headersWhenDefaultsThenAllDefaultsWritten() { - this.http.headers(withDefaults()); - assertHeaders(); - } - - @Test - public void headersWhenDefaultsInLambdaThenAllDefaultsWritten() { - this.http.headers(withDefaults()); - assertHeaders(); - } - - @Test - public void headersWhenCacheDisableThenCacheNotWritten() { - expectHeaderNamesNotPresent(HttpHeaders.CACHE_CONTROL, HttpHeaders.PRAGMA, HttpHeaders.EXPIRES); - this.http.headers((headers) -> headers.cache((cache) -> cache.disable())); - assertHeaders(); - } - - @Test - public void headersWhenCacheDisableInLambdaThenCacheNotWritten() { - expectHeaderNamesNotPresent(HttpHeaders.CACHE_CONTROL, HttpHeaders.PRAGMA, HttpHeaders.EXPIRES); - // @formatter:off - this.http.headers((headers) -> headers - .cache((cache) -> cache.disable()) - ); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenContentOptionsDisableThenContentTypeOptionsNotWritten() { - expectHeaderNamesNotPresent(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS); - this.http.headers((headers) -> headers.contentTypeOptions((options) -> options.disable())); - assertHeaders(); - } - - @Test - public void headersWhenContentOptionsDisableInLambdaThenContentTypeOptionsNotWritten() { - expectHeaderNamesNotPresent(ContentTypeOptionsServerHttpHeadersWriter.X_CONTENT_OPTIONS); - // @formatter:off - this.http - .headers((headers) -> headers - .contentTypeOptions((contentTypeOptions) -> contentTypeOptions.disable() - )); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenHstsDisableThenHstsNotWritten() { - expectHeaderNamesNotPresent(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY); - this.http.headers((headers) -> headers.hsts((hsts) -> hsts.disable())); - assertHeaders(); - } - - @Test - public void headersWhenHstsDisableInLambdaThenHstsNotWritten() { - expectHeaderNamesNotPresent(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY); - // @formatter:off - this.http.headers((headers) -> headers - .hsts((hsts) -> hsts.disable()) - ); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenHstsCustomThenCustomHstsWritten() { - this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY); - this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, - "max-age=60"); - // @formatter:off - this.http.headers((headers) -> headers - .hsts((hsts) -> hsts - .maxAge(Duration.ofSeconds(60)) - .includeSubdomains(false))); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenHstsCustomInLambdaThenCustomHstsWritten() { - this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY); - this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, - "max-age=60"); - // @formatter:off - this.http.headers( - (headers) -> headers - .hsts((hsts) -> hsts - .maxAge(Duration.ofSeconds(60)) - .includeSubdomains(false) - ) - ); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenHstsCustomWithPreloadThenCustomHstsWritten() { - this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY); - this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, - "max-age=60 ; includeSubDomains ; preload"); - // @formatter:off - this.http.headers((headers) -> headers - .hsts((hsts) -> hsts - .maxAge(Duration.ofSeconds(60)) - .preload(true))); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenHstsCustomWithPreloadInLambdaThenCustomHstsWritten() { - this.expectedHeaders.remove(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY); - this.expectedHeaders.add(StrictTransportSecurityServerHttpHeadersWriter.STRICT_TRANSPORT_SECURITY, - "max-age=60 ; includeSubDomains ; preload"); - // @formatter:off - this.http.headers((headers) -> headers - .hsts((hsts) -> hsts - .maxAge(Duration.ofSeconds(60)) - .preload(true) - ) - ); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenFrameOptionsDisableThenFrameOptionsNotWritten() { - expectHeaderNamesNotPresent(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS); - // @formatter:off - this.http.headers((headers) -> headers - .frameOptions((options) -> options.disable())); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenFrameOptionsDisableInLambdaThenFrameOptionsNotWritten() { - expectHeaderNamesNotPresent(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS); - // @formatter:off - this.http.headers((headers) -> headers - .frameOptions((frameOptions) -> frameOptions - .disable() - ) - ); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenFrameOptionsModeThenFrameOptionsCustomMode() { - this.expectedHeaders.set(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "SAMEORIGIN"); - // @formatter:off - this.http.headers((headers) -> headers - .frameOptions((frameOptions) -> frameOptions - .mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN))); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenFrameOptionsModeInLambdaThenFrameOptionsCustomMode() { - this.expectedHeaders.set(XFrameOptionsServerHttpHeadersWriter.X_FRAME_OPTIONS, "SAMEORIGIN"); - // @formatter:off - this.http.headers((headers) -> headers - .frameOptions((frameOptions) -> frameOptions - .mode(XFrameOptionsServerHttpHeadersWriter.Mode.SAMEORIGIN) - ) - ); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenXssProtectionDisableThenXssProtectionNotWritten() { - expectHeaderNamesNotPresent("X-Xss-Protection"); - // @formatter:off - this.http.headers((headers) -> headers - .xssProtection((xss) -> xss.disable())); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenXssProtectionDisableInLambdaThenXssProtectionNotWritten() { - expectHeaderNamesNotPresent("X-Xss-Protection"); - // @formatter:off - this.http.headers((headers) -> headers - .xssProtection((xssProtection) -> xssProtection - .disable() - ) - ); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenXssProtectionValueDisabledThenXssProtectionWritten() { - this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0"); - // @formatter:off - this.http.headers((headers) -> headers - .xssProtection((xss) -> xss - .headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.DISABLED))); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenXssProtectionValueEnabledThenXssProtectionWritten() { - this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1"); - // @formatter:off - this.http.headers((headers) -> headers - .xssProtection((xss) -> xss - .headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.ENABLED))); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenXssProtectionValueEnabledModeBlockThenXssProtectionWritten() { - this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "1; mode=block"); - // @formatter:off - this.http.headers((headers) -> headers - .xssProtection((xss) -> xss - .headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.ENABLED_MODE_BLOCK))); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenXssProtectionValueDisabledInLambdaThenXssProtectionWritten() { - this.expectedHeaders.set(XXssProtectionServerHttpHeadersWriter.X_XSS_PROTECTION, "0"); - // @formatter:off - this.http.headers((headers) -> headers - .xssProtection((xssProtection) -> xssProtection.headerValue(XXssProtectionServerHttpHeadersWriter.HeaderValue.DISABLED) - )); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenFeaturePolicyEnabledThenFeaturePolicyWritten() { - String policyDirectives = "Feature-Policy"; - this.expectedHeaders.add(FeaturePolicyServerHttpHeadersWriter.FEATURE_POLICY, policyDirectives); - // @formatter:off - this.http.headers((headers) -> headers - .featurePolicy(policyDirectives)); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenContentSecurityPolicyEnabledThenFeaturePolicyWritten() { - String policyDirectives = "default-src 'self'"; - this.expectedHeaders.add(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY, - policyDirectives); - // @formatter:off - this.http.headers((headers) -> headers - .contentSecurityPolicy((csp) -> csp.policyDirectives(policyDirectives))); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenContentSecurityPolicyEnabledWithDefaultsInLambdaThenDefaultPolicyWritten() { - String expectedPolicyDirectives = "default-src 'self'"; - this.expectedHeaders.add(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY, - expectedPolicyDirectives); - // @formatter:off - this.http.headers((headers) -> headers - .contentSecurityPolicy(withDefaults()) - ); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenContentSecurityPolicyEnabledInLambdaThenContentSecurityPolicyWritten() { - String policyDirectives = "default-src 'self' *.trusted.com"; - this.expectedHeaders.add(ContentSecurityPolicyServerHttpHeadersWriter.CONTENT_SECURITY_POLICY, - policyDirectives); - // @formatter:off - this.http.headers((headers) -> headers - .contentSecurityPolicy((csp) -> csp - .policyDirectives(policyDirectives) - ) - ); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenReferrerPolicyEnabledThenFeaturePolicyWritten() { - this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY, - ReferrerPolicy.NO_REFERRER.getPolicy()); - // @formatter:off - this.http.headers((headers) -> headers - .referrerPolicy(Customizer.withDefaults())); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenReferrerPolicyEnabledInLambdaThenReferrerPolicyWritten() { - this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY, - ReferrerPolicy.NO_REFERRER.getPolicy()); - // @formatter:off - this.http.headers((headers) -> headers - .referrerPolicy(withDefaults() - ) - ); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenReferrerPolicyCustomEnabledThenFeaturePolicyCustomWritten() { - this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY, - ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE.getPolicy()); - // @formatter:off - this.http.headers((headers) -> headers - .referrerPolicy((referrer) -> referrer.policy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE))); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenReferrerPolicyCustomEnabledInLambdaThenCustomReferrerPolicyWritten() { - this.expectedHeaders.add(ReferrerPolicyServerHttpHeadersWriter.REFERRER_POLICY, - ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE.getPolicy()); - // @formatter:off - this.http.headers((headers) -> headers - .referrerPolicy((referrerPolicy) -> referrerPolicy - .policy(ReferrerPolicy.NO_REFERRER_WHEN_DOWNGRADE) - ) - ); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenCustomHeadersWriter() { - this.expectedHeaders.add(CUSTOM_HEADER, CUSTOM_VALUE); - // @formatter:off - this.http.headers((headers) -> headers - .writer((exchange) -> Mono.just(exchange) - .doOnNext((it) -> it.getResponse().getHeaders().add(CUSTOM_HEADER, CUSTOM_VALUE)) - .then() - ) - ); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenCrossOriginPoliciesCustomEnabledThenCustomCrossOriginPoliciesWritten() { - this.expectedHeaders.add(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY, - CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS - .getPolicy()); - this.expectedHeaders.add(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY, - CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP.getPolicy()); - this.expectedHeaders.add(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY, - CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN.getPolicy()); - // @formatter:off - this.http.headers((headers) -> headers - .crossOriginOpenerPolicy((opener) -> opener - .policy(CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS)) - .crossOriginEmbedderPolicy((embedder) -> embedder - .policy(CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP)) - .crossOriginResourcePolicy((resource) -> resource - .policy(CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN))); - // @formatter:on - assertHeaders(); - } - - @Test - public void headersWhenCrossOriginPoliciesCustomEnabledInLambdaThenCustomCrossOriginPoliciesWritten() { - this.expectedHeaders.add(CrossOriginOpenerPolicyServerHttpHeadersWriter.OPENER_POLICY, - CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS - .getPolicy()); - this.expectedHeaders.add(CrossOriginEmbedderPolicyServerHttpHeadersWriter.EMBEDDER_POLICY, - CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP.getPolicy()); - this.expectedHeaders.add(CrossOriginResourcePolicyServerHttpHeadersWriter.RESOURCE_POLICY, - CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN.getPolicy()); - // @formatter:off - this.http.headers((headers) -> headers - .crossOriginOpenerPolicy((policy) -> policy - .policy(CrossOriginOpenerPolicyServerHttpHeadersWriter.CrossOriginOpenerPolicy.SAME_ORIGIN_ALLOW_POPUPS) - ) - .crossOriginEmbedderPolicy((policy) -> policy - .policy(CrossOriginEmbedderPolicyServerHttpHeadersWriter.CrossOriginEmbedderPolicy.REQUIRE_CORP) - ) - .crossOriginResourcePolicy((policy) -> policy - .policy(CrossOriginResourcePolicyServerHttpHeadersWriter.CrossOriginResourcePolicy.SAME_ORIGIN) - )); - // @formatter:on - assertHeaders(); - } - - private void expectHeaderNamesNotPresent(String... headerNames) { - for (String headerName : headerNames) { - this.expectedHeaders.remove(headerName); - this.headerNamesNotPresent.add(headerName); - } - } - - private void assertHeaders() { - WebTestClient client = buildClient(); - FluxExchangeResult response = client.get() - .uri("https://example.com/") - .exchange() - .returnResult(String.class); - HttpHeaders responseHeaders = response.getResponseHeaders(); - if (!this.expectedHeaders.isEmpty()) { - this.expectedHeaders.forEach( - (headerName, headerValues) -> assertThat(responseHeaders.get(headerName)).isEqualTo(headerValues)); - } - if (!this.headerNamesNotPresent.isEmpty()) { - assertThat(responseHeaders.headerNames()).doesNotContainAnyElementsOf(this.headerNamesNotPresent); - } - } - - private WebTestClient buildClient() { - return WebTestClientBuilder.bindToWebFilters(this.http.build()).build(); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/web/server/HttpsRedirectSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/HttpsRedirectSpecTests.java deleted file mode 100644 index 5a65ba893c0..00000000000 --- a/config/src/test/java/org/springframework/security/config/web/server/HttpsRedirectSpecTests.java +++ /dev/null @@ -1,268 +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.web.server; - -import org.apache.http.HttpHeaders; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.web.PortMapper; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.config.EnableWebFlux; - -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * Tests for {@link HttpsRedirectSpecTests} - * - * @author Josh Cummings - */ -@ExtendWith(SpringTestContextExtension.class) -public class HttpsRedirectSpecTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - WebTestClient client; - - @Autowired - public void setApplicationContext(ApplicationContext context) { - // @formatter:off - this.client = WebTestClient - .bindToApplicationContext(context) - .build(); - // @formatter:on - } - - @Test - public void getWhenSecureThenDoesNotRedirect() { - this.spring.register(RedirectToHttpConfig.class).autowire(); - // @formatter:off - this.client.get() - .uri("https://localhost") - .exchange() - .expectStatus().isNotFound(); - // @formatter:on - } - - @Test - public void getWhenInsecureThenRespondsWithRedirectToSecure() { - this.spring.register(RedirectToHttpConfig.class).autowire(); - // @formatter:off - this.client.get() - .uri("http://localhost") - .exchange() - .expectStatus().isFound() - .expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost"); - // @formatter:on - } - - @Test - public void getWhenInsecureAndRedirectConfiguredInLambdaThenRespondsWithRedirectToSecure() { - this.spring.register(RedirectToHttpsInLambdaConfig.class).autowire(); - // @formatter:off - this.client.get() - .uri("http://localhost") - .exchange() - .expectStatus().isFound() - .expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost"); - // @formatter:on - } - - @Test - public void getWhenInsecureAndPathRequiresTransportSecurityThenRedirects() { - this.spring.register(SometimesRedirectToHttpsConfig.class).autowire(); - // @formatter:off - this.client.get() - .uri("http://localhost:8080") - .exchange() - .expectStatus().isNotFound(); - this.client.get() - .uri("http://localhost:8080/secure") - .exchange() - .expectStatus().isFound() - .expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:8443/secure"); - // @formatter:on - } - - @Test - public void getWhenInsecureAndPathRequiresTransportSecurityInLambdaThenRedirects() { - this.spring.register(SometimesRedirectToHttpsInLambdaConfig.class).autowire(); - // @formatter:off - this.client.get() - .uri("http://localhost:8080") - .exchange() - .expectStatus().isNotFound(); - this.client.get() - .uri("http://localhost:8080/secure") - .exchange() - .expectStatus().isFound() - .expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:8443/secure"); - // @formatter:on - } - - @Test - public void getWhenInsecureAndUsingCustomPortMapperThenRespondsWithRedirectToSecurePort() { - this.spring.register(RedirectToHttpsViaCustomPortsConfig.class).autowire(); - PortMapper portMapper = this.spring.getContext().getBean(PortMapper.class); - given(portMapper.lookupHttpsPort(4080)).willReturn(4443); - // @formatter:off - this.client.get() - .uri("http://localhost:4080") - .exchange() - .expectStatus().isFound() - .expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:4443"); - // @formatter:on - } - - @Test - public void getWhenInsecureAndUsingCustomPortMapperInLambdaThenRespondsWithRedirectToSecurePort() { - this.spring.register(RedirectToHttpsViaCustomPortsInLambdaConfig.class).autowire(); - PortMapper portMapper = this.spring.getContext().getBean(PortMapper.class); - given(portMapper.lookupHttpsPort(4080)).willReturn(4443); - // @formatter:off - this.client.get() - .uri("http://localhost:4080") - .exchange() - .expectStatus().isFound() - .expectHeader().valueEquals(HttpHeaders.LOCATION, "https://localhost:4443"); - // @formatter:on - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class RedirectToHttpConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .redirectToHttps(withDefaults()); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class RedirectToHttpsInLambdaConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .redirectToHttps(withDefaults()); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class SometimesRedirectToHttpsConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .redirectToHttps((https) -> https - .httpsRedirectWhen(new PathPatternParserServerWebExchangeMatcher("/secure"))); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class SometimesRedirectToHttpsInLambdaConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .redirectToHttps((redirectToHttps) -> redirectToHttps - .httpsRedirectWhen(new PathPatternParserServerWebExchangeMatcher("/secure")) - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class RedirectToHttpsViaCustomPortsConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .redirectToHttps((https) -> https - .portMapper(portMapper())); - // @formatter:on - return http.build(); - } - - @Bean - PortMapper portMapper() { - return mock(PortMapper.class); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class RedirectToHttpsViaCustomPortsInLambdaConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .redirectToHttps((redirectToHttps) -> redirectToHttps - .portMapper(portMapper()) - ); - // @formatter:on - return http.build(); - } - - @Bean - PortMapper portMapper() { - return mock(PortMapper.class); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java deleted file mode 100644 index 99e68827ddf..00000000000 --- a/config/src/test/java/org/springframework/security/config/web/server/LogoutSpecTests.java +++ /dev/null @@ -1,310 +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.web.server; - -import org.jspecify.annotations.Nullable; -import org.junit.jupiter.api.Test; -import org.openqa.selenium.WebDriver; -import reactor.core.publisher.Mono; - -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder; -import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler; -import org.springframework.security.web.server.context.ServerSecurityContextRepository; -import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository; -import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * @author Shazin Sadakath - * @since 5.0 - */ -public class LogoutSpecTests { - - private ServerHttpSecurity http = ServerHttpSecurityConfigurationBuilder.httpWithDefaultAuthentication(); - - @Test - public void defaultLogout() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated()) - .formLogin(withDefaults()) - .build(); - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - // @formatter:on - FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage - .to(driver, FormLoginTests.DefaultLoginPage.class) - .assertAt(); - // @formatter:off - loginPage = loginPage.loginForm() - .username("user") - .password("invalid") - .submit(FormLoginTests.DefaultLoginPage.class) - .assertError(); - FormLoginTests.HomePage homePage = loginPage.loginForm() - .username("user") - .password("password") - .submit(FormLoginTests.HomePage.class); - // @formatter:on - homePage.assertAt(); - loginPage = FormLoginTests.DefaultLogoutPage.to(driver).assertAt().logout(); - loginPage.assertAt().assertLogout(); - } - - @Test - public void customLogout() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated()) - .formLogin(withDefaults()) - .logout((logout) -> logout - .requiresLogout(ServerWebExchangeMatchers.pathMatchers("/custom-logout"))) - .build(); - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - // @formatter:on - FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage - .to(driver, FormLoginTests.DefaultLoginPage.class) - .assertAt(); - // @formatter:off - loginPage = loginPage.loginForm() - .username("user") - .password("invalid") - .submit(FormLoginTests.DefaultLoginPage.class) - .assertError(); - FormLoginTests.HomePage homePage = loginPage.loginForm() - .username("user") - .password("password") - .submit(FormLoginTests.HomePage.class); - homePage.assertAt(); - // @formatter:on - driver.get("http://localhost/custom-logout"); - FormLoginTests.DefaultLoginPage.create(driver).assertAt().assertLogout(); - } - - @Test - public void logoutWhenCustomLogoutInLambdaThenCustomLogoutUsed() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated() - ) - .formLogin(withDefaults()) - .logout((logout) -> logout - .requiresLogout(ServerWebExchangeMatchers.pathMatchers("/custom-logout")) - ) - .build(); - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - // @formatter:on - FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage - .to(driver, FormLoginTests.DefaultLoginPage.class) - .assertAt(); - // @formatter:off - loginPage = loginPage.loginForm() - .username("user") - .password("invalid") - .submit(FormLoginTests.DefaultLoginPage.class) - .assertError(); - FormLoginTests.HomePage homePage = loginPage.loginForm() - .username("user").password("password") - .submit(FormLoginTests.HomePage.class); - // @formatter:on - homePage.assertAt(); - driver.get("http://localhost/custom-logout"); - FormLoginTests.DefaultLoginPage.create(driver).assertAt().assertLogout(); - } - - @Test - public void logoutWhenDisabledThenDefaultLogoutPageDoesNotExist() { - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated()) - .formLogin(withDefaults()) - .logout((logout) -> logout.disable()) - .build(); - WebTestClient webTestClient = WebTestClientBuilder - .bindToControllerAndWebFilters(HomeController.class, securityWebFilter) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - // @formatter:on - FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage - .to(driver, FormLoginTests.DefaultLoginPage.class) - .assertAt(); - // @formatter:off - FormLoginTests.HomePage homePage = loginPage.loginForm() - .username("user") - .password("password") - .submit(FormLoginTests.HomePage.class); - // @formatter:on - homePage.assertAt(); - FormLoginTests.DefaultLogoutPage.to(driver); - assertThat(driver.getPageSource()).isEmpty(); - } - - @Test - public void logoutWhenCustomSecurityContextRepositoryThenLogsOut() { - WebSessionServerSecurityContextRepository repository = new WebSessionServerSecurityContextRepository(); - repository.setSpringSecurityContextAttrName("CUSTOM_CONTEXT_ATTR"); - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .securityContextRepository(repository) - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated()) - .formLogin(withDefaults()) - .logout(withDefaults()) - .build(); - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - // @formatter:on - FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage - .to(driver, FormLoginTests.DefaultLoginPage.class) - .assertAt(); - // @formatter:off - FormLoginTests.HomePage homePage = loginPage.loginForm() - .username("user") - .password("password") - .submit(FormLoginTests.HomePage.class); - // @formatter:on - homePage.assertAt(); - FormLoginTests.DefaultLogoutPage.to(driver).assertAt().logout(); - FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class).assertAt(); - } - - @Test - public void multipleLogoutHandlers() { - InMemorySecurityContextRepository repository = new InMemorySecurityContextRepository(); - MultiValueMap logoutData = new LinkedMultiValueMap<>(); - ServerLogoutHandler handler1 = (exchange, authentication) -> { - logoutData.add("handler-header", "value1"); - return Mono.empty(); - }; - ServerLogoutHandler handler2 = (exchange, authentication) -> { - logoutData.add("handler-header", "value2"); - return Mono.empty(); - }; - // @formatter:off - SecurityWebFilterChain securityWebFilter = this.http - .securityContextRepository(repository) - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated()) - .formLogin(withDefaults()) - .logout((logoutSpec) -> logoutSpec.logoutHandler((handlers) -> { - handlers.add(handler1); - handlers.add(0, handler2); - })) - .build(); - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(securityWebFilter) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - // @formatter:on - FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage - .to(driver, FormLoginTests.DefaultLoginPage.class) - .assertAt(); - // @formatter:off - loginPage = loginPage.loginForm() - .username("user") - .password("invalid") - .submit(FormLoginTests.DefaultLoginPage.class) - .assertError(); - FormLoginTests.HomePage homePage = loginPage.loginForm() - .username("user") - .password("password") - .submit(FormLoginTests.HomePage.class); - // @formatter:on - homePage.assertAt(); - SecurityContext savedContext = repository.getSavedContext(); - assertThat(savedContext).isNotNull(); - assertThat(savedContext.getAuthentication()).isInstanceOf(UsernamePasswordAuthenticationToken.class); - - loginPage = FormLoginTests.DefaultLogoutPage.to(driver).assertAt().logout(); - loginPage.assertAt().assertLogout(); - assertThat(logoutData).hasSize(1); - assertThat(logoutData.get("handler-header")).containsExactly("value2", "value1"); - savedContext = repository.getSavedContext(); - assertThat(savedContext).isNull(); - } - - private static class InMemorySecurityContextRepository implements ServerSecurityContextRepository { - - @Nullable private SecurityContext savedContext; - - @Override - public Mono save(ServerWebExchange exchange, SecurityContext context) { - this.savedContext = context; - return Mono.empty(); - } - - @Override - public Mono load(ServerWebExchange exchange) { - return Mono.justOrEmpty(this.savedContext); - } - - @Nullable private SecurityContext getSavedContext() { - return this.savedContext; - } - - } - - @RestController - public static class HomeController { - - @GetMapping("/") - public String ok() { - return "ok"; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ClientSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2ClientSpecTests.java deleted file mode 100644 index a082fd7020f..00000000000 --- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ClientSpecTests.java +++ /dev/null @@ -1,431 +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.web.server; - -import java.net.URI; -import java.util.Set; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.authentication.ReactiveAuthenticationManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken; -import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; -import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository; -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver; -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.core.AuthorizationGrantType; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses; -import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; -import org.springframework.security.test.context.support.WithMockUser; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; -import org.springframework.security.web.server.savedrequest.ServerRequestCache; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.config.EnableWebFlux; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * @author Rob Winch - * @author Parikshit Dutta - * @since 5.1 - */ -@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) -@SecurityTestExecutionListeners -public class OAuth2ClientSpecTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - private WebTestClient client; - - private ClientRegistration registration = TestClientRegistrations.clientRegistration().build(); - - @Autowired - public void setApplicationContext(ApplicationContext context) { - // @formatter:off - this.client = WebTestClient - .bindToApplicationContext(context) - .build(); - // @formatter:on - } - - @Test - @WithMockUser - public void registeredOAuth2AuthorizedClientWhenAuthenticatedThenRedirects() { - this.spring.register(Config.class, AuthorizedClientController.class).autowire(); - ReactiveClientRegistrationRepository repository = this.spring.getContext() - .getBean(ReactiveClientRegistrationRepository.class); - ServerOAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext() - .getBean(ServerOAuth2AuthorizedClientRepository.class); - given(repository.findByRegistrationId(any())) - .willReturn(Mono.just(TestClientRegistrations.clientRegistration().build())); - given(authorizedClientRepository.loadAuthorizedClient(any(), any(), any())).willReturn(Mono.empty()); - // @formatter:off - this.client.get() - .uri("/") - .exchange() - .expectStatus().is3xxRedirection(); - // @formatter:on - } - - @Test - public void registeredOAuth2AuthorizedClientWhenAnonymousThenRedirects() { - this.spring.register(Config.class, AuthorizedClientController.class).autowire(); - ReactiveClientRegistrationRepository repository = this.spring.getContext() - .getBean(ReactiveClientRegistrationRepository.class); - ServerOAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext() - .getBean(ServerOAuth2AuthorizedClientRepository.class); - given(repository.findByRegistrationId(any())) - .willReturn(Mono.just(TestClientRegistrations.clientRegistration().build())); - given(authorizedClientRepository.loadAuthorizedClient(any(), any(), any())).willReturn(Mono.empty()); - // @formatter:off - this.client.get() - .uri("/") - .exchange() - .expectStatus().is3xxRedirection(); - // @formatter:on - } - - @Test - public void oauth2ClientWhenCustomObjectsThenUsed() { - this.spring - .register(ClientRegistrationConfig.class, OAuth2ClientCustomConfig.class, AuthorizedClientController.class) - .autowire(); - OAuth2ClientCustomConfig config = this.spring.getContext().getBean(OAuth2ClientCustomConfig.class); - ServerAuthenticationConverter converter = config.authenticationConverter; - ReactiveAuthenticationManager manager = config.manager; - ServerAuthorizationRequestRepository authorizationRequestRepository = config.authorizationRequestRepository; - ServerOAuth2AuthorizationRequestResolver resolver = config.resolver; - ServerRequestCache requestCache = config.requestCache; - OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() - .redirectUri("/authorize/oauth2/code/registration-id") - .build(); - OAuth2AuthorizationResponse authorizationResponse = TestOAuth2AuthorizationResponses.success() - .redirectUri("/authorize/oauth2/code/registration-id") - .build(); - OAuth2AuthorizationExchange authorizationExchange = new OAuth2AuthorizationExchange(authorizationRequest, - authorizationResponse); - OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes(); - OAuth2AuthorizationCodeAuthenticationToken result = new OAuth2AuthorizationCodeAuthenticationToken( - this.registration, authorizationExchange, accessToken); - given(authorizationRequestRepository.loadAuthorizationRequest(any())) - .willReturn(Mono.just(authorizationRequest)); - given(resolver.resolve(any())).willReturn(Mono.empty()); - given(converter.convert(any())).willReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c"))); - given(manager.authenticate(any())).willReturn(Mono.just(result)); - given(requestCache.getRedirectUri(any())).willReturn(Mono.just(URI.create("/saved-request"))); - // @formatter:off - this.client.get() - .uri((uriBuilder) -> uriBuilder - .path("/authorize/oauth2/code/registration-id") - .queryParam(OAuth2ParameterNames.CODE, "code") - .queryParam(OAuth2ParameterNames.STATE, "state") - .build() - ) - .exchange() - .expectStatus().is3xxRedirection(); - // @formatter:on - verify(converter).convert(any()); - verify(manager).authenticate(any()); - verify(requestCache).getRedirectUri(any()); - verify(resolver).resolve(any()); - } - - @Test - public void oauth2ClientWhenCustomObjectsInLambdaThenUsed() { - this.spring - .register(ClientRegistrationConfig.class, OAuth2ClientInLambdaCustomConfig.class, - AuthorizedClientController.class) - .autowire(); - OAuth2ClientInLambdaCustomConfig config = this.spring.getContext() - .getBean(OAuth2ClientInLambdaCustomConfig.class); - ServerAuthenticationConverter converter = config.authenticationConverter; - ReactiveAuthenticationManager manager = config.manager; - ServerAuthorizationRequestRepository authorizationRequestRepository = config.authorizationRequestRepository; - ServerRequestCache requestCache = config.requestCache; - OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() - .redirectUri("/authorize/oauth2/code/registration-id") - .build(); - OAuth2AuthorizationResponse authorizationResponse = TestOAuth2AuthorizationResponses.success() - .redirectUri("/authorize/oauth2/code/registration-id") - .build(); - OAuth2AuthorizationExchange authorizationExchange = new OAuth2AuthorizationExchange(authorizationRequest, - authorizationResponse); - OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes(); - OAuth2AuthorizationCodeAuthenticationToken result = new OAuth2AuthorizationCodeAuthenticationToken( - this.registration, authorizationExchange, accessToken); - given(authorizationRequestRepository.loadAuthorizationRequest(any())) - .willReturn(Mono.just(authorizationRequest)); - given(converter.convert(any())).willReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c"))); - given(manager.authenticate(any())).willReturn(Mono.just(result)); - given(requestCache.getRedirectUri(any())).willReturn(Mono.just(URI.create("/saved-request"))); - // @formatter:off - this.client.get() - .uri((uriBuilder) -> uriBuilder - .path("/authorize/oauth2/code/registration-id") - .queryParam(OAuth2ParameterNames.CODE, "code") - .queryParam(OAuth2ParameterNames.STATE, "state") - .build() - ) - .exchange() - .expectStatus().is3xxRedirection(); - // @formatter:on - verify(converter).convert(any()); - verify(manager).authenticate(any()); - verify(requestCache).getRedirectUri(any()); - } - - @Test - @SuppressWarnings("unchecked") - public void oauth2ClientWhenCustomAccessTokenResponseClientThenUsed() { - this.spring.register(OAuth2ClientBeanConfig.class, AuthorizedClientController.class).autowire(); - ReactiveClientRegistrationRepository clientRegistrationRepository = this.spring.getContext() - .getBean(ReactiveClientRegistrationRepository.class); - given(clientRegistrationRepository.findByRegistrationId(any())).willReturn(Mono.just(this.registration)); - ServerOAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext() - .getBean(ServerOAuth2AuthorizedClientRepository.class); - given(authorizedClientRepository.saveAuthorizedClient(any(OAuth2AuthorizedClient.class), - any(Authentication.class), any(ServerWebExchange.class))) - .willReturn(Mono.empty()); - ServerAuthorizationRequestRepository authorizationRequestRepository = this.spring - .getContext() - .getBean(ServerAuthorizationRequestRepository.class); - OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request() - .redirectUri("/authorize/oauth2/code/registration-id") - .build(); - given(authorizationRequestRepository.loadAuthorizationRequest(any(ServerWebExchange.class))) - .willReturn(Mono.just(authorizationRequest)); - given(authorizationRequestRepository.removeAuthorizationRequest(any(ServerWebExchange.class))) - .willReturn(Mono.just(authorizationRequest)); - ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient = this.spring - .getContext() - .getBean(ReactiveOAuth2AccessTokenResponseClient.class); - OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("token") - .tokenType(OAuth2AccessToken.TokenType.BEARER) - .scopes(Set.of()) - .expiresIn(300) - .build(); - given(accessTokenResponseClient.getTokenResponse(any(OAuth2AuthorizationCodeGrantRequest.class))) - .willReturn(Mono.just(accessTokenResponse)); - // @formatter:off - this.client.get() - .uri((uriBuilder) -> uriBuilder - .path("/authorize/oauth2/code/registration-id") - .queryParam(OAuth2ParameterNames.CODE, "code") - .queryParam(OAuth2ParameterNames.STATE, "state") - .build() - ) - .exchange() - .expectStatus().is3xxRedirection(); - // @formatter:on - ArgumentCaptor grantRequestArgumentCaptor = ArgumentCaptor - .forClass(OAuth2AuthorizationCodeGrantRequest.class); - verify(accessTokenResponseClient).getTokenResponse(grantRequestArgumentCaptor.capture()); - OAuth2AuthorizationCodeGrantRequest grantRequest = grantRequestArgumentCaptor.getValue(); - assertThat(grantRequest.getClientRegistration()).isEqualTo(this.registration); - assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.AUTHORIZATION_CODE); - assertThat(grantRequest.getAuthorizationExchange().getAuthorizationRequest()).isEqualTo(authorizationRequest); - assertThat(grantRequest.getAuthorizationExchange().getAuthorizationResponse().getCode()).isEqualTo("code"); - assertThat(grantRequest.getAuthorizationExchange().getAuthorizationResponse().getState()).isEqualTo("state"); - assertThat(grantRequest.getAuthorizationExchange().getAuthorizationResponse().getRedirectUri()) - .startsWith("/authorize/oauth2/code/registration-id"); - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class Config { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .oauth2Client(withDefaults()); - // @formatter:on - return http.build(); - } - - @Bean - ReactiveClientRegistrationRepository clientRegistrationRepository() { - return mock(ReactiveClientRegistrationRepository.class); - } - - @Bean - ServerOAuth2AuthorizedClientRepository authorizedClientRepository() { - return mock(ServerOAuth2AuthorizedClientRepository.class); - } - - } - - @RestController - static class AuthorizedClientController { - - @GetMapping("/") - String home(@RegisteredOAuth2AuthorizedClient("github") OAuth2AuthorizedClient authorizedClient) { - return "home"; - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class ClientRegistrationConfig { - - private ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build(); - - @Bean - InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() { - return new InMemoryReactiveClientRegistrationRepository(this.clientRegistration); - } - - } - - @Configuration - static class OAuth2ClientCustomConfig { - - ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class); - - ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class); - - ServerAuthorizationRequestRepository authorizationRequestRepository = mock( - ServerAuthorizationRequestRepository.class); - - ServerOAuth2AuthorizationRequestResolver resolver = mock(ServerOAuth2AuthorizationRequestResolver.class); - - ServerRequestCache requestCache = mock(ServerRequestCache.class); - - @Bean - SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) { - // @formatter:off - http - .oauth2Client((client) -> client - .authenticationConverter(this.authenticationConverter) - .authenticationManager(this.manager) - .authorizationRequestRepository(this.authorizationRequestRepository) - .authorizationRequestResolver(this.resolver)) - .requestCache((c) -> c.requestCache(this.requestCache)); - // @formatter:on - return http.build(); - } - - } - - @Configuration - static class OAuth2ClientInLambdaCustomConfig { - - ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class); - - ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class); - - ServerAuthorizationRequestRepository authorizationRequestRepository = mock( - ServerAuthorizationRequestRepository.class); - - ServerRequestCache requestCache = mock(ServerRequestCache.class); - - @Bean - SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) { - // @formatter:off - http - .oauth2Client((oauth2Client) -> oauth2Client - .authenticationConverter(this.authenticationConverter) - .authenticationManager(this.manager) - .authorizationRequestRepository(this.authorizationRequestRepository)) - .requestCache((c) -> c.requestCache(this.requestCache)); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class OAuth2ClientBeanConfig { - - @Bean - SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { - // @formatter:off - http - .oauth2Client((oauth2Client) -> oauth2Client - .authorizationRequestRepository(authorizationRequestRepository()) - ); - // @formatter:on - return http.build(); - } - - @Bean - @SuppressWarnings("unchecked") - ServerAuthorizationRequestRepository authorizationRequestRepository() { - return mock(ServerAuthorizationRequestRepository.class); - } - - @Bean - @SuppressWarnings("unchecked") - ReactiveOAuth2AccessTokenResponseClient authorizationCodeAccessTokenResponseClient() { - return mock(ReactiveOAuth2AccessTokenResponseClient.class); - } - - @Bean - ReactiveClientRegistrationRepository clientRegistrationRepository() { - return mock(ReactiveClientRegistrationRepository.class); - } - - @Bean - ServerOAuth2AuthorizedClientRepository authorizedClientRepository() { - return mock(ServerOAuth2AuthorizedClientRepository.class); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java deleted file mode 100644 index 6286e91619d..00000000000 --- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java +++ /dev/null @@ -1,1162 +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.web.server; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.stubbing.Answer; -import org.openqa.selenium.WebDriver; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.security.authentication.ReactiveAuthenticationManager; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.config.oauth2.client.CommonOAuth2Provider; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration; -import org.springframework.security.config.web.server.ServerHttpSecurity.OAuth2LoginSpec.OidcSessionRegistryAuthenticationWebFilter; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContext; -import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.security.core.userdetails.ReactiveUserDetailsService; -import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken; -import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken; -import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest; -import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient; -import org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeReactiveAuthenticationManager; -import org.springframework.security.oauth2.client.oidc.server.session.ReactiveOidcSessionRegistry; -import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest; -import org.springframework.security.oauth2.client.oidc.web.server.logout.OidcClientInitiatedServerLogoutSuccessHandler; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.client.userinfo.DefaultReactiveOAuth2UserService; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService; -import org.springframework.security.oauth2.client.web.server.DefaultServerOAuth2AuthorizationRequestResolver; -import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository; -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver; -import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; -import org.springframework.security.oauth2.core.OAuth2AccessToken; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.TestOAuth2AccessTokens; -import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; -import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationExchanges; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationRequests; -import org.springframework.security.oauth2.core.endpoint.TestOAuth2AuthorizationResponses; -import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames; -import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; -import org.springframework.security.oauth2.core.oidc.user.OidcUser; -import org.springframework.security.oauth2.core.oidc.user.TestOidcUsers; -import org.springframework.security.oauth2.core.user.DefaultOAuth2User; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.security.oauth2.core.user.TestOAuth2Users; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.JwtValidationException; -import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; -import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory; -import org.springframework.security.oauth2.jwt.TestJwts; -import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.security.web.server.WebFilterChainProxy; -import org.springframework.security.web.server.WebFilterExchange; -import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler; -import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler; -import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; -import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler; -import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler; -import org.springframework.security.web.server.authentication.logout.SecurityContextServerLogoutHandler; -import org.springframework.security.web.server.context.ServerSecurityContextRepository; -import org.springframework.security.web.server.savedrequest.ServerRequestCache; -import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.reactive.config.EnableWebFlux; -import org.springframework.web.server.ServerWebExchange; -import org.springframework.web.server.WebFilter; -import org.springframework.web.server.WebFilterChain; -import org.springframework.web.server.WebHandler; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.security.config.Customizer.withDefaults; - -/** - * @author Rob Winch - * @author Eddú Meléndez - * @since 5.1 - */ -@ExtendWith(SpringTestContextExtension.class) -public class OAuth2LoginTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - private WebTestClient client; - - @Autowired - private WebFilterChainProxy springSecurity; - - private static ClientRegistration github = CommonOAuth2Provider.GITHUB.getBuilder("github") - .clientId("client") - .clientSecret("secret") - .build(); - - private static ClientRegistration google = CommonOAuth2Provider.GOOGLE.getBuilder("google") - .clientId("client") - .clientSecret("secret") - .build(); - - // @formatter:off - private static ClientRegistration clientCredentials = TestClientRegistrations.clientCredentials() - .build(); - // @formatter:on - - @Autowired - public void setApplicationContext(ApplicationContext context) { - if (context.getBeanNamesForType(WebHandler.class).length > 0) { - // @formatter:off - this.client = WebTestClient - .bindToApplicationContext(context) - .build(); - // @formatter:on - } - } - - @Test - public void defaultLoginPageWithMultipleClientRegistrationsThenLinks() { - this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class).autowire(); - // @formatter:off - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(this.springSecurity) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - FormLoginTests.DefaultLoginPage loginPage = FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class) - .assertAt() - .assertLoginFormNotPresent() - .oauth2Login() - .assertClientRegistrationByName(OAuth2LoginTests.github.getClientName()) - .and(); - // @formatter:on - } - - @Test - public void defaultLoginPageWithSingleClientRegistrationThenRedirect() { - this.spring.register(OAuth2LoginWithSingleClientRegistrations.class).autowire(); - // @formatter:off - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(new GitHubWebFilter(), this.springSecurity) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - // @formatter:on - driver.get("http://localhost/"); - assertThat(driver.getCurrentUrl()).startsWith("https://github.com/login/oauth/authorize"); - } - - // gh-9457 - @Test - public void defaultLoginPageWithAuthorizationCodeAndClientCredentialsClientRegistrationThenRedirect() { - this.spring.register(OAuth2LoginWithAuthorizationCodeAndClientCredentialsClientRegistration.class).autowire(); - // @formatter:off - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(new GitHubWebFilter(), this.springSecurity) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - // @formatter:on - driver.get("http://localhost/"); - assertThat(driver.getCurrentUrl()).startsWith("https://github.com/login/oauth/authorize"); - } - - @Test - public void defaultLoginPageWithSingleClientRegistrationAndFormLoginThenLinks() { - this.spring.register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2LoginWithFormLogin.class).autowire(); - // @formatter:off - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(new GitHubWebFilter(), this.springSecurity) - .build(); - WebDriver driver = WebTestClientHtmlUnitDriverBuilder - .webTestClientSetup(webTestClient) - .build(); - FormLoginTests.HomePage.to(driver, FormLoginTests.DefaultLoginPage.class) - .assertAt() - .assertLoginFormPresent() - .oauth2Login() - .assertClientRegistrationByName(OAuth2LoginTests.github.getClientName()); - // @formatter:on - } - - // gh-8118 - @Test - public void defaultLoginPageWithSingleClientRegistrationAndXhrRequestThenDoesNotRedirectForAuthorization() { - this.spring.register(OAuth2LoginWithSingleClientRegistrations.class, WebFluxConfig.class).autowire(); - // @formatter:off - this.client.get() - .uri("/") - .header("X-Requested-With", "XMLHttpRequest") - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().valueEquals(HttpHeaders.LOCATION, "/login"); - // @formatter:on - } - - @Test - public void defaultLoginPageWithOAuth2LoginHttpBasicAndXhrRequestThenUnauthorized() { - this.spring - .register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2LoginWithHttpBasic.class, - WebFluxConfig.class) - .autowire(); - // @formatter:off - this.client.get() - .uri("/") - .header("X-Requested-With", "XMLHttpRequest") - .exchange() - .expectStatus().isUnauthorized(); - // @formatter:on - } - - @Test - public void defaultLoginPageWhenCustomLoginPageThenGeneratedLoginPageDoesNotExist() { - this.spring - .register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2LoginWithCustomLoginPage.class, - WebFluxConfig.class) - .autowire(); - // @formatter:off - this.client.get() - .uri("/login") - .exchange() - .expectStatus().isNotFound(); - // @formatter:on - } - - @Test - public void oauth2LoginWhenCustomLoginPageAndSingleClientRegistrationThenRedirectsToLoginPage() { - this.spring - .register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2LoginWithCustomLoginPage.class, - WebFluxConfig.class) - .autowire(); - // @formatter:off - this.client.get() - .uri("/") - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().valueEquals(HttpHeaders.LOCATION, "/login"); - // @formatter:on - } - - @Test - public void oauth2LoginWhenCustomLoginPageAndMultipleClientRegistrationsThenRedirectsToLoginPage() { - this.spring - .register(OAuth2LoginWithMultipleClientRegistrations.class, OAuth2LoginWithCustomLoginPage.class, - WebFluxConfig.class) - .autowire(); - // @formatter:off - this.client.get() - .uri("/") - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().valueEquals(HttpHeaders.LOCATION, "/login"); - // @formatter:on - } - - @Test - public void oauth2LoginWhenProviderLoginPageAndMultipleClientRegistrationsThenRedirectsToProvider() { - this.spring - .register(OAuth2LoginWithMultipleClientRegistrations.class, OAuth2LoginWithProviderLoginPage.class, - WebFluxConfig.class) - .autowire(); - // @formatter:off - this.client.get() - .uri("/") - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().valueEquals(HttpHeaders.LOCATION, "/oauth2/authorization/github"); - // @formatter:on - } - - @Test - public void oauth2AuthorizeWhenCustomObjectsThenUsed() { - this.spring - .register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2AuthorizeWithMockObjectsConfig.class, - AuthorizedClientController.class) - .autowire(); - OAuth2AuthorizeWithMockObjectsConfig config = this.spring.getContext() - .getBean(OAuth2AuthorizeWithMockObjectsConfig.class); - ServerOAuth2AuthorizedClientRepository authorizedClientRepository = config.authorizedClientRepository; - ServerAuthorizationRequestRepository authorizationRequestRepository = config.authorizationRequestRepository; - ServerRequestCache requestCache = config.requestCache; - given(authorizedClientRepository.loadAuthorizedClient(any(), any(), any())).willReturn(Mono.empty()); - given(authorizationRequestRepository.saveAuthorizationRequest(any(), any())).willReturn(Mono.empty()); - given(requestCache.removeMatchingRequest(any())).willReturn(Mono.empty()); - given(requestCache.saveRequest(any())).willReturn(Mono.empty()); - // @formatter:off - this.client.get() - .uri("/") - .exchange() - .expectStatus().is3xxRedirection(); - // @formatter:on - verify(authorizedClientRepository).loadAuthorizedClient(any(), any(), any()); - verify(authorizationRequestRepository).saveAuthorizationRequest(any(), any()); - verify(requestCache).saveRequest(any()); - } - - @Test - public void oauth2LoginWhenCustomObjectsThenUsed() { - this.spring - .register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2LoginMockAuthenticationManagerConfig.class) - .autowire(); - String redirectLocation = "/custom-redirect-location"; - WebTestClient webTestClient = WebTestClientBuilder.bindToWebFilters(this.springSecurity).build(); - OAuth2LoginMockAuthenticationManagerConfig config = this.spring.getContext() - .getBean(OAuth2LoginMockAuthenticationManagerConfig.class); - ServerAuthenticationConverter converter = config.authenticationConverter; - ReactiveAuthenticationManager manager = config.manager; - ServerWebExchangeMatcher matcher = config.matcher; - ServerOAuth2AuthorizationRequestResolver resolver = config.resolver; - ServerAuthenticationSuccessHandler successHandler = config.successHandler; - OAuth2AuthorizationExchange exchange = TestOAuth2AuthorizationExchanges.success(); - OAuth2User user = TestOAuth2Users.create(); - OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes(); - OAuth2LoginAuthenticationToken result = new OAuth2LoginAuthenticationToken(github, exchange, user, - user.getAuthorities(), accessToken); - given(converter.convert(any())).willReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c"))); - given(manager.authenticate(any())).willReturn(Mono.just(result)); - given(matcher.matches(any())).willReturn(ServerWebExchangeMatcher.MatchResult.match()); - given(resolver.resolve(any())).willReturn(Mono.empty()); - given(successHandler.onAuthenticationSuccess(any(), any())).willAnswer((Answer>) (invocation) -> { - WebFilterExchange webFilterExchange = invocation.getArgument(0); - Authentication authentication = invocation.getArgument(1); - return new RedirectServerAuthenticationSuccessHandler(redirectLocation) - .onAuthenticationSuccess(webFilterExchange, authentication); - }); - // @formatter:off - webTestClient.get() - .uri("/login/oauth2/code/github") - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().valueEquals("Location", redirectLocation); - // @formatter:on - verify(converter).convert(any()); - verify(manager).authenticate(any()); - verify(matcher).matches(any()); - verify(resolver).resolve(any()); - verify(successHandler).onAuthenticationSuccess(any(), any()); - } - - @Test - public void oauth2LoginFailsWhenCustomObjectsThenUsed() { - this.spring - .register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2LoginMockAuthenticationManagerConfig.class) - .autowire(); - String redirectLocation = "/custom-redirect-location"; - String failureRedirectLocation = "/failure-redirect-location"; - // @formatter:off - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(this.springSecurity) - .build(); - // @formatter:on - OAuth2LoginMockAuthenticationManagerConfig config = this.spring.getContext() - .getBean(OAuth2LoginMockAuthenticationManagerConfig.class); - ServerAuthenticationConverter converter = config.authenticationConverter; - ReactiveAuthenticationManager manager = config.manager; - ServerWebExchangeMatcher matcher = config.matcher; - ServerOAuth2AuthorizationRequestResolver resolver = config.resolver; - ServerAuthenticationSuccessHandler successHandler = config.successHandler; - ServerAuthenticationFailureHandler failureHandler = config.failureHandler; - given(converter.convert(any())).willReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c"))); - given(manager.authenticate(any())) - .willReturn(Mono.error(new OAuth2AuthenticationException(new OAuth2Error("error"), "message"))); - given(matcher.matches(any())).willReturn(ServerWebExchangeMatcher.MatchResult.match()); - given(resolver.resolve(any())).willReturn(Mono.empty()); - given(successHandler.onAuthenticationSuccess(any(), any())).willAnswer((Answer>) (invocation) -> { - WebFilterExchange webFilterExchange = invocation.getArgument(0); - Authentication authentication = invocation.getArgument(1); - return new RedirectServerAuthenticationSuccessHandler(redirectLocation) - .onAuthenticationSuccess(webFilterExchange, authentication); - }); - given(failureHandler.onAuthenticationFailure(any(), any())).willAnswer((Answer>) (invocation) -> { - WebFilterExchange webFilterExchange = invocation.getArgument(0); - AuthenticationException authenticationException = invocation.getArgument(1); - return new RedirectServerAuthenticationFailureHandler(failureRedirectLocation) - .onAuthenticationFailure(webFilterExchange, authenticationException); - }); - // @formatter:off - webTestClient.get() - .uri("/login/oauth2/code/github") - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().valueEquals("Location", failureRedirectLocation); - // @formatter:on - verify(converter).convert(any()); - verify(manager).authenticate(any()); - verify(matcher).matches(any()); - verify(resolver).resolve(any()); - verify(failureHandler).onAuthenticationFailure(any(), any()); - } - - @Test - public void oauth2LoginWhenCustomObjectsInLambdaThenUsed() { - this.spring - .register(OAuth2LoginWithSingleClientRegistrations.class, - OAuth2LoginMockAuthenticationManagerInLambdaConfig.class) - .autowire(); - String redirectLocation = "/custom-redirect-location"; - WebTestClient webTestClient = WebTestClientBuilder.bindToWebFilters(this.springSecurity).build(); - OAuth2LoginMockAuthenticationManagerInLambdaConfig config = this.spring.getContext() - .getBean(OAuth2LoginMockAuthenticationManagerInLambdaConfig.class); - ServerAuthenticationConverter converter = config.authenticationConverter; - ReactiveAuthenticationManager manager = config.manager; - ServerWebExchangeMatcher matcher = config.matcher; - ServerOAuth2AuthorizationRequestResolver resolver = config.resolver; - ServerAuthenticationSuccessHandler successHandler = config.successHandler; - OAuth2AuthorizationExchange exchange = TestOAuth2AuthorizationExchanges.success(); - OAuth2User user = TestOAuth2Users.create(); - OAuth2AccessToken accessToken = TestOAuth2AccessTokens.noScopes(); - OAuth2LoginAuthenticationToken result = new OAuth2LoginAuthenticationToken(github, exchange, user, - user.getAuthorities(), accessToken); - given(converter.convert(any())).willReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c"))); - given(manager.authenticate(any())).willReturn(Mono.just(result)); - given(matcher.matches(any())).willReturn(ServerWebExchangeMatcher.MatchResult.match()); - given(resolver.resolve(any())).willReturn(Mono.empty()); - given(successHandler.onAuthenticationSuccess(any(), any())).willAnswer((Answer>) (invocation) -> { - WebFilterExchange webFilterExchange = invocation.getArgument(0); - Authentication authentication = invocation.getArgument(1); - return new RedirectServerAuthenticationSuccessHandler(redirectLocation) - .onAuthenticationSuccess(webFilterExchange, authentication); - }); - // @formatter:off - webTestClient.get() - .uri("/login/oauth2/code/github") - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().valueEquals("Location", redirectLocation); - // @formatter:on - verify(converter).convert(any()); - verify(manager).authenticate(any()); - verify(matcher).matches(any()); - verify(resolver).resolve(any()); - verify(successHandler).onAuthenticationSuccess(any(), any()); - } - - @Test - public void oauth2LoginWhenCustomBeansThenUsed() { - this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class, OAuth2LoginWithCustomBeansConfig.class) - .autowire(); - // @formatter:off - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(this.springSecurity) - .build(); - // @formatter:on - OAuth2LoginWithCustomBeansConfig config = this.spring.getContext() - .getBean(OAuth2LoginWithCustomBeansConfig.class); - OAuth2AuthorizationRequest request = TestOAuth2AuthorizationRequests.request().scope("openid").build(); - OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses.success().build(); - OAuth2AuthorizationExchange exchange = new OAuth2AuthorizationExchange(request, response); - OAuth2AccessToken accessToken = TestOAuth2AccessTokens.scopes("openid"); - OAuth2AuthorizationCodeAuthenticationToken token = new OAuth2AuthorizationCodeAuthenticationToken(google, - exchange, accessToken); - ServerAuthenticationConverter converter = config.authenticationConverter; - given(converter.convert(any())).willReturn(Mono.just(token)); - ServerSecurityContextRepository securityContextRepository = config.securityContextRepository; - given(securityContextRepository.save(any(), any())).willReturn(Mono.empty()); - given(securityContextRepository.load(any())).willReturn(authentication(token)); - Map additionalParameters = new HashMap<>(); - additionalParameters.put(OidcParameterNames.ID_TOKEN, "id-token"); - // @formatter:off - OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse - .withToken(accessToken.getTokenValue()) - .tokenType(accessToken.getTokenType()) - .scopes(accessToken.getScopes()) - .additionalParameters(additionalParameters) - .build(); - // @formatter:on - ReactiveOAuth2AccessTokenResponseClient tokenResponseClient = config.tokenResponseClient; - given(tokenResponseClient.getTokenResponse(any())).willReturn(Mono.just(accessTokenResponse)); - OidcUser user = TestOidcUsers.create(); - ReactiveOAuth2UserService userService = config.userService; - given(userService.loadUser(any())).willReturn(Mono.just(user)); - ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver = config.authorizationRequestResolver; - // @formatter:off - webTestClient.get() - .uri("/login/oauth2/code/google") - .exchange() - .expectStatus().is3xxRedirection(); - // @formatter:on - verify(config.jwtDecoderFactory).createDecoder(any()); - verify(tokenResponseClient).getTokenResponse(any()); - verify(securityContextRepository).save(any(), any()); - verify(authorizationRequestResolver).resolve(any()); - } - - // gh-5562 - @Test - public void oauth2LoginWhenAccessTokenRequestFailsThenDefaultRedirectToLogin() { - this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class, OAuth2LoginWithCustomBeansConfig.class) - .autowire(); - // @formatter:off - WebTestClient webTestClient = WebTestClientBuilder - .bindToWebFilters(this.springSecurity) - .build(); - OAuth2AuthorizationRequest request = TestOAuth2AuthorizationRequests - .request() - .scope("openid") - .build(); - // @formatter:on - OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses.success().build(); - OAuth2AuthorizationExchange exchange = new OAuth2AuthorizationExchange(request, response); - OAuth2AccessToken accessToken = TestOAuth2AccessTokens.scopes("openid"); - OAuth2AuthorizationCodeAuthenticationToken authenticationToken = new OAuth2AuthorizationCodeAuthenticationToken( - google, exchange, accessToken); - OAuth2LoginWithCustomBeansConfig config = this.spring.getContext() - .getBean(OAuth2LoginWithCustomBeansConfig.class); - ServerAuthenticationConverter converter = config.authenticationConverter; - given(converter.convert(any())).willReturn(Mono.just(authenticationToken)); - ReactiveOAuth2AccessTokenResponseClient tokenResponseClient = config.tokenResponseClient; - OAuth2Error oauth2Error = new OAuth2Error("invalid_request", "Invalid request", null); - given(tokenResponseClient.getTokenResponse(any())).willThrow(new OAuth2AuthenticationException(oauth2Error)); - // @formatter:off - webTestClient.get() - .uri("/login/oauth2/code/google") - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().valueEquals("Location", "/login?error"); - // @formatter:on - } - - // gh-6484 - @Test - public void oauth2LoginWhenIdTokenValidationFailsThenDefaultRedirectToLogin() { - this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class, OAuth2LoginWithCustomBeansConfig.class) - .autowire(); - WebTestClient webTestClient = WebTestClientBuilder.bindToWebFilters(this.springSecurity).build(); - OAuth2LoginWithCustomBeansConfig config = this.spring.getContext() - .getBean(OAuth2LoginWithCustomBeansConfig.class); - // @formatter:off - OAuth2AuthorizationRequest request = TestOAuth2AuthorizationRequests - .request() - .scope("openid") - .build(); - OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses - .success() - .build(); - // @formatter:on - OAuth2AuthorizationExchange exchange = new OAuth2AuthorizationExchange(request, response); - OAuth2AccessToken accessToken = TestOAuth2AccessTokens.scopes("openid"); - OAuth2AuthorizationCodeAuthenticationToken authenticationToken = new OAuth2AuthorizationCodeAuthenticationToken( - google, exchange, accessToken); - ServerAuthenticationConverter converter = config.authenticationConverter; - given(converter.convert(any())).willReturn(Mono.just(authenticationToken)); - Map additionalParameters = new HashMap<>(); - additionalParameters.put(OidcParameterNames.ID_TOKEN, "id-token"); - // @formatter:off - OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse - .withToken(accessToken.getTokenValue()) - .tokenType(accessToken.getTokenType()) - .scopes(accessToken.getScopes()) - .additionalParameters(additionalParameters) - .build(); - // @formatter:on - ReactiveOAuth2AccessTokenResponseClient tokenResponseClient = config.tokenResponseClient; - given(tokenResponseClient.getTokenResponse(any())).willReturn(Mono.just(accessTokenResponse)); - ReactiveJwtDecoderFactory jwtDecoderFactory = config.jwtDecoderFactory; - OAuth2Error oauth2Error = new OAuth2Error("invalid_id_token", "Invalid ID Token", null); - given(jwtDecoderFactory.createDecoder(any())).willReturn((token) -> Mono - .error(new JwtValidationException("ID Token validation failed", Collections.singleton(oauth2Error)))); - // @formatter:off - webTestClient.get() - .uri("/login/oauth2/code/google") - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().valueEquals("Location", "/login?error"); - // @formatter:on - } - - @Test - public void logoutWhenUsingOidcLogoutHandlerThenRedirects() { - this.spring.register(OAuth2LoginConfigWithOidcLogoutSuccessHandler.class).autowire(); - OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(TestOidcUsers.create(), - AuthorityUtils.NO_AUTHORITIES, getBean(ClientRegistration.class).getRegistrationId()); - ServerSecurityContextRepository repository = getBean(ServerSecurityContextRepository.class); - given(repository.load(any())).willReturn(authentication(token)); - // @formatter:off - this.client.post() - .uri("/logout") - .exchange() - .expectHeader().valueEquals("Location", "https://logout?id_token_hint=id-token"); - // @formatter:on - } - - // gh-8609 - @Test - public void oauth2LoginWhenAuthenticationConverterFailsThenDefaultRedirectToLogin() { - this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class).autowire(); - WebTestClient webTestClient = WebTestClientBuilder.bindToWebFilters(this.springSecurity).build(); - // @formatter:off - webTestClient.get() - .uri("/login/oauth2/code/google") - .exchange() - .expectStatus().is3xxRedirection() - .expectHeader().valueEquals("Location", "/login?error"); - // @formatter:on - } - - @Test - public void oauth2LoginWhenOidcSessionRegistryThenUses() { - this.spring.register(OAuth2LoginWithOidcSessionRegistry.class).autowire(); - SecurityWebFilterChain chain = this.spring.getContext().getBean(SecurityWebFilterChain.class); - assertThat(chain.getWebFilters() - .filter((filter) -> filter instanceof OidcSessionRegistryAuthenticationWebFilter) - .collectList() - .block()).isNotEmpty(); - } - - // gh-14558 - @Test - public void oauth2LoginWhenDefaultsThenNoOidcSessionRegistry() { - this.spring.register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2LoginConfig.class).autowire(); - SecurityWebFilterChain chain = this.spring.getContext().getBean(SecurityWebFilterChain.class); - assertThat(chain.getWebFilters() - .filter((filter) -> filter instanceof OidcSessionRegistryAuthenticationWebFilter) - .collectList() - .block()).isEmpty(); - } - - @Test - public void oauth2LoginWhenOauth2UserServiceBeanPresent() { - this.spring.register(OAuth2LoginWithMultipleClientRegistrations.class, OAuth2LoginWithOauth2UserService.class) - .autowire(); - WebTestClient webTestClient = WebTestClientBuilder.bindToWebFilters(this.springSecurity).build(); - OAuth2LoginWithOauth2UserService config = this.spring.getContext() - .getBean(OAuth2LoginWithOauth2UserService.class); - OAuth2AuthorizationRequest request = TestOAuth2AuthorizationRequests.request().scope("openid").build(); - OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses.success().build(); - OAuth2AuthorizationExchange exchange = new OAuth2AuthorizationExchange(request, response); - OAuth2AccessToken accessToken = TestOAuth2AccessTokens.scopes("openid"); - OAuth2AuthorizationCodeAuthenticationToken token = new OAuth2AuthorizationCodeAuthenticationToken(google, - exchange, accessToken); - ServerAuthenticationConverter converter = config.authenticationConverter; - given(converter.convert(any())).willReturn(Mono.just(token)); - ServerSecurityContextRepository securityContextRepository = config.securityContextRepository; - given(securityContextRepository.save(any(), any())).willReturn(Mono.empty()); - given(securityContextRepository.load(any())).willReturn(authentication(token)); - Map additionalParameters = new HashMap<>(); - additionalParameters.put(OidcParameterNames.ID_TOKEN, "id-token"); - OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue()) - .tokenType(accessToken.getTokenType()) - .scopes(accessToken.getScopes()) - .additionalParameters(additionalParameters) - .build(); - ReactiveOAuth2AccessTokenResponseClient tokenResponseClient = config.tokenResponseClient; - given(tokenResponseClient.getTokenResponse(any())).willReturn(Mono.just(accessTokenResponse)); - ReactiveOAuth2UserService userService = config.reactiveOAuth2UserService; - given(userService.loadUser(any())).willReturn(Mono - .just(new DefaultOAuth2User(AuthorityUtils.createAuthorityList("USER"), Map.of("sub", "subject"), "sub"))); - webTestClient.get().uri("/login/oauth2/code/google").exchange().expectStatus().is3xxRedirection(); - verify(userService).loadUser(any()); - - } - - Mono authentication(Authentication authentication) { - SecurityContext context = new SecurityContextImpl(); - context.setAuthentication(authentication); - return Mono.just(context); - } - - T getBean(Class beanClass) { - return this.spring.getContext().getBean(beanClass); - } - - @Configuration - static class OAuth2LoginWithOauth2UserService { - - ReactiveOAuth2AccessTokenResponseClient tokenResponseClient = mock( - ReactiveOAuth2AccessTokenResponseClient.class); - - ReactiveOAuth2UserService reactiveOAuth2UserService = mock( - DefaultReactiveOAuth2UserService.class); - - ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class); - - ServerSecurityContextRepository securityContextRepository = mock(ServerSecurityContextRepository.class); - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - http.authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) - .oauth2Login((c) -> c.authenticationConverter(this.authenticationConverter) - .securityContextRepository(this.securityContextRepository)); - return http.build(); - } - - @Bean - ReactiveOAuth2UserService customOAuth2UserService() { - return this.reactiveOAuth2UserService; - } - - @Bean - ReactiveJwtDecoderFactory jwtDecoderFactory() { - return (clientRegistration) -> (token) -> { - Map claims = new HashMap<>(); - claims.put(IdTokenClaimNames.SUB, "subject"); - claims.put(IdTokenClaimNames.ISS, "http://localhost/issuer"); - claims.put(IdTokenClaimNames.AUD, Collections.singletonList("client")); - claims.put(IdTokenClaimNames.AZP, "client"); - return Mono.just(TestJwts.jwt().claims((c) -> c.putAll(claims)).build()); - }; - } - - @Bean - ReactiveOAuth2AccessTokenResponseClient requestReactiveOAuth2AccessTokenResponseClient() { - return this.tokenResponseClient; - } - - } - - @Configuration - @EnableWebFluxSecurity - static class OAuth2LoginWithMultipleClientRegistrations { - - @Bean - InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() { - return new InMemoryReactiveClientRegistrationRepository(github, google); - } - - } - - @EnableWebFlux - static class WebFluxConfig { - - } - - @Configuration - @EnableWebFluxSecurity - static class OAuth2LoginWithSingleClientRegistrations { - - @Bean - InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() { - return new InMemoryReactiveClientRegistrationRepository(github); - } - - } - - @Configuration - @EnableWebFluxSecurity - static class OAuth2LoginWithAuthorizationCodeAndClientCredentialsClientRegistration { - - @Bean - InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() { - return new InMemoryReactiveClientRegistrationRepository(github, clientCredentials); - } - - } - - @EnableWebFlux - static class OAuth2LoginConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) - .oauth2Login(Customizer.withDefaults()); - // @formatter:on - return http.build(); - } - - } - - @EnableWebFlux - static class OAuth2AuthorizeWithMockObjectsConfig { - - ServerOAuth2AuthorizedClientRepository authorizedClientRepository = mock( - ServerOAuth2AuthorizedClientRepository.class); - - ServerAuthorizationRequestRepository authorizationRequestRepository = mock( - ServerAuthorizationRequestRepository.class); - - ServerRequestCache requestCache = mock(ServerRequestCache.class); - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .requestCache((cache) -> cache - .requestCache(this.requestCache)) - .oauth2Login((login) -> login - .authorizationRequestRepository(this.authorizationRequestRepository)); - // @formatter:on - return http.build(); - } - - @Bean - ServerOAuth2AuthorizedClientRepository authorizedClientRepository() { - return this.authorizedClientRepository; - } - - } - - @RestController - static class AuthorizedClientController { - - @GetMapping("/") - String home(@RegisteredOAuth2AuthorizedClient("github") OAuth2AuthorizedClient authorizedClient) { - return "home"; - } - - } - - @Configuration - static class OAuth2LoginWithFormLogin { - - @Bean - SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) { - ReactiveUserDetailsService reactiveUserDetailsService = ReactiveAuthenticationTestConfiguration - .userDetailsService(); - ReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager( - reactiveUserDetailsService); - http.authenticationManager(authenticationManager); - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated()) - .oauth2Login(withDefaults()) - .formLogin(withDefaults()); - // @formatter:on - return http.build(); - } - - } - - @Configuration - static class OAuth2LoginWithHttpBasic { - - @Bean - SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) { - ReactiveUserDetailsService reactiveUserDetailsService = ReactiveAuthenticationTestConfiguration - .userDetailsService(); - ReactiveAuthenticationManager authenticationManager = new UserDetailsRepositoryReactiveAuthenticationManager( - reactiveUserDetailsService); - http.authenticationManager(authenticationManager); - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated()) - .oauth2Login(withDefaults()) - .httpBasic(withDefaults()); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebFluxSecurity - static class OAuth2LoginWithCustomLoginPage { - - @Bean - SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .pathMatchers(HttpMethod.GET, "/login").permitAll() - .anyExchange().authenticated() - ) - .oauth2Login((oauth2) -> oauth2 - .loginPage("/login") - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebFluxSecurity - static class OAuth2LoginWithProviderLoginPage { - - @Bean - SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - // @formatter:off - http.authorizeExchange((authorize) -> authorize - .anyExchange().authenticated() - ) - .oauth2Login((oauth2) -> oauth2 - .loginPage("/oauth2/authorization/github") - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - static class OAuth2LoginMockAuthenticationManagerConfig { - - ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class); - - ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class); - - ServerWebExchangeMatcher matcher = mock(ServerWebExchangeMatcher.class); - - ServerOAuth2AuthorizationRequestResolver resolver = mock(ServerOAuth2AuthorizationRequestResolver.class); - - ServerAuthenticationSuccessHandler successHandler = mock(ServerAuthenticationSuccessHandler.class); - - ServerAuthenticationFailureHandler failureHandler = mock(ServerAuthenticationFailureHandler.class); - - @Bean - SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated()) - .oauth2Login((login) -> login - .authenticationConverter(this.authenticationConverter) - .authenticationManager(this.manager) - .authenticationMatcher(this.matcher) - .authorizationRequestResolver(this.resolver) - .authenticationSuccessHandler(this.successHandler) - .authenticationFailureHandler(this.failureHandler)); - // @formatter:on - return http.build(); - } - - } - - @Configuration - static class OAuth2LoginMockAuthenticationManagerInLambdaConfig { - - ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class); - - ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class); - - ServerWebExchangeMatcher matcher = mock(ServerWebExchangeMatcher.class); - - ServerOAuth2AuthorizationRequestResolver resolver = mock(ServerOAuth2AuthorizationRequestResolver.class); - - ServerAuthenticationSuccessHandler successHandler = mock(ServerAuthenticationSuccessHandler.class); - - @Bean - SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated() - ) - .oauth2Login((oauth2) -> oauth2 - .authenticationConverter(this.authenticationConverter) - .authenticationManager(this.manager) - .authenticationMatcher(this.matcher) - .authorizationRequestResolver(this.resolver) - .authenticationSuccessHandler(this.successHandler) - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - static class OAuth2LoginWithCustomBeansConfig { - - ServerAuthenticationConverter authenticationConverter = mock(ServerAuthenticationConverter.class); - - ReactiveOAuth2AccessTokenResponseClient tokenResponseClient = mock( - ReactiveOAuth2AccessTokenResponseClient.class); - - ReactiveOAuth2UserService userService = mock(ReactiveOAuth2UserService.class); - - ReactiveJwtDecoderFactory jwtDecoderFactory = spy(new JwtDecoderFactory()); - - ServerSecurityContextRepository securityContextRepository = mock(ServerSecurityContextRepository.class); - - ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver = spy( - new DefaultServerOAuth2AuthorizationRequestResolver(new InMemoryReactiveClientRegistrationRepository( - TestClientRegistrations.clientRegistration().build()))); - - @Bean - SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated()) - .oauth2Login((login) -> login - .authenticationConverter(this.authenticationConverter) - .authenticationManager(authenticationManager()) - .securityContextRepository(this.securityContextRepository)); - return http.build(); - // @formatter:on - } - - private ReactiveAuthenticationManager authenticationManager() { - OidcAuthorizationCodeReactiveAuthenticationManager oidc = new OidcAuthorizationCodeReactiveAuthenticationManager( - this.tokenResponseClient, this.userService); - oidc.setJwtDecoderFactory(jwtDecoderFactory()); - return oidc; - } - - @Bean - ReactiveJwtDecoderFactory jwtDecoderFactory() { - return this.jwtDecoderFactory; - } - - @Bean - ServerOAuth2AuthorizationRequestResolver authorizationRequestResolver() { - return this.authorizationRequestResolver; - } - - @Bean - ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient() { - return this.tokenResponseClient; - } - - private static class JwtDecoderFactory implements ReactiveJwtDecoderFactory { - - @Override - public ReactiveJwtDecoder createDecoder(ClientRegistration clientRegistration) { - return getJwtDecoder(); - } - - private ReactiveJwtDecoder getJwtDecoder() { - return (token) -> { - Map claims = new HashMap<>(); - claims.put(IdTokenClaimNames.SUB, "subject"); - claims.put(IdTokenClaimNames.ISS, "http://localhost/issuer"); - claims.put(IdTokenClaimNames.AUD, Collections.singletonList("client")); - claims.put(IdTokenClaimNames.AZP, "client"); - Jwt jwt = TestJwts.jwt().claims((c) -> c.putAll(claims)).build(); - return Mono.just(jwt); - }; - } - - } - - } - - @EnableWebFlux - @Configuration - @EnableWebFluxSecurity - static class OAuth2LoginConfigWithOidcLogoutSuccessHandler { - - private final ServerSecurityContextRepository repository = mock(ServerSecurityContextRepository.class); - - private final ClientRegistration withLogout = TestClientRegistrations.clientRegistration() - .providerConfigurationMetadata(Collections.singletonMap("end_session_endpoint", "https://logout")) - .build(); - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .csrf((csrf) -> csrf.disable()) - .logout((logout) -> logout - // avoid using mock ServerSecurityContextRepository for logout - .logoutHandler(new SecurityContextServerLogoutHandler()) - .logoutSuccessHandler( - new OidcClientInitiatedServerLogoutSuccessHandler( - new InMemoryReactiveClientRegistrationRepository(this.withLogout)))) - .securityContextRepository(this.repository); - // @formatter:on - return http.build(); - } - - @Bean - ServerSecurityContextRepository securityContextRepository() { - return this.repository; - } - - @Bean - ClientRegistration clientRegistration() { - return this.withLogout; - } - - } - - @Configuration - @EnableWebFluxSecurity - static class OAuth2LoginWithOidcSessionRegistry { - - private final ReactiveOidcSessionRegistry registry = mock(ReactiveOidcSessionRegistry.class); - - private final ReactiveClientRegistrationRepository clients = new InMemoryReactiveClientRegistrationRepository( - TestClientRegistrations.clientRegistration().build()); - - @Bean - SecurityWebFilterChain filterChain(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) - .oauth2Login((oauth2) -> oauth2 - .clientRegistrationRepository(this.clients) - .oidcSessionRegistry(this.registry) - ); - // @formatter:on - return http.build(); - } - - @Bean - ReactiveOidcSessionRegistry oidcSessionRegistry() { - return this.registry; - } - - } - - static class GitHubWebFilter implements WebFilter { - - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - if (exchange.getRequest().getURI().getHost().equals("github.com")) { - return exchange.getResponse().setComplete(); - } - return chain.filter(exchange); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java deleted file mode 100644 index 2c3c662fc4b..00000000000 --- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java +++ /dev/null @@ -1,1169 +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.web.server; - -import java.io.IOException; -import java.math.BigInteger; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.RSAPublicKeySpec; -import java.util.Base64; -import java.util.Collections; -import java.util.Optional; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import jakarta.annotation.PreDestroy; -import okhttp3.mockwebserver.Dispatcher; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; -import org.apache.http.HttpHeaders; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.BeanCreationException; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.NoUniqueBeanDefinitionException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.security.authentication.AbstractAuthenticationToken; -import org.springframework.security.authentication.BadCredentialsException; -import org.springframework.security.authentication.ReactiveAuthenticationManager; -import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.core.OAuth2AuthenticatedPrincipal; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.jwt.Jwt; -import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; -import org.springframework.security.oauth2.jwt.TestJwts; -import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; -import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverter; -import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter; -import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint; -import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; -import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler; -import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.context.support.GenericWebApplicationContext; -import org.springframework.web.reactive.DispatcherHandler; -import org.springframework.web.reactive.config.EnableWebFlux; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; -import static org.hamcrest.CoreMatchers.startsWith; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -/** - * Tests for - * {@link org.springframework.security.config.web.server.ServerHttpSecurity.OAuth2ResourceServerSpec} - */ -@ExtendWith({ SpringTestContextExtension.class }) -public class OAuth2ResourceServerSpecTests { - - private String expired = "eyJhbGciOiJSUzI1NiJ9.eyJleHAiOjE1MzUwMzc4OTd9.jqZDDjfc2eysX44lHXEIr9XFd2S8vjIZHCccZU-dRWMRJNsQ1QN5VNnJGklqJBXJR4qgla6cmVqPOLkUHDb0sL0nxM5XuzQaG5ZzKP81RV88shFyAiT0fD-6nl1k-Fai-Fu-VkzSpNXgeONoTxDaYhdB-yxmgrgsApgmbOTE_9AcMk-FQDXQ-pL9kynccFGV0lZx4CA7cyknKN7KBxUilfIycvXODwgKCjj_1WddLTCNGYogJJSg__7NoxzqbyWd3udbHVjqYq7GsMMrGB4_2kBD4CkghOSNcRHbT_DIXowxfAVT7PAg7Q0E5ruZsr2zPZacEUDhJ6-wbvlA0FAOUg"; - - private String messageReadToken = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJtb2NrLXN1YmplY3QiLCJzY29wZSI6Im1lc3NhZ2U6cmVhZCIsImV4cCI6NDY4ODY0MTQxM30.cRl1bv_dDYcAN5U4NlIVKj8uu4mLMwjABF93P4dShiq-GQ-owzaqTSlB4YarNFgV3PKQvT9wxN1jBpGribvISljakoC0E8wDV-saDi8WxN-qvImYsn1zLzYFiZXCfRIxCmonJpydeiAPRxMTPtwnYDS9Ib0T_iA80TBGd-INhyxUUfrwRW5sqKRbjUciRJhpp7fW2ZYXmi9iPt3HDjRQA4IloJZ7f4-spt5Q9wl5HcQTv1t4XrX4eqhVbE5cCoIkFQnKPOc-jhVM44_eazLU6Xk-CCXP8C_UT5pX0luRS2cJrVFfHp2IR_AWxC-shItg6LNEmNFD4Zc-JLZcr0Q86Q"; - - private String messageReadTokenWithKid = "eyJraWQiOiJvbmUiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJtb2NrLXN1YmplY3QiLCJzY29wZSI6Im1lc3NhZ2U6cmVhZCIsImV4cCI6NDY4ODY0MTQ2MX0.Arg3IjlNb_nkEIZpcWAQquvoiaeF_apJzO5ZxSzUQEWixH1Y7yrsW2uco452a7OtAKDNT09IplK8126z_hdI_RRk0CXVsGZYe1qppNIVLEPGv4rHxND4bPv1YA91Q8vG-vDk9rod7EvAuZU1tEP_pWkSkZVAmfuP43bP5FQcO6Q31Aba7Yb7O5qWn9U2MjruPSFvTsIx3hSXgTuJxhNCKeHnTCmv2WdjYWatR7-VujBlHd-ZolysXm7-JPz3kI75omnomG2UqnKkI76sczIpm4ieOp3fSyv-QR-i-3Z_eJ9hS3Ox46Y9NJS6Z-y1g3X0fjVyhLiIJkFV3VA5HrSf_A"; - - private String unsignedToken = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOi0yMDMzMjI0OTcsImp0aSI6IjEyMyIsInR5cCI6IkpXVCJ9."; - - // @formatter:off - private String jwkSet = "{\n" - + " \"keys\":[\n" - + " {\n" - + " \"kty\":\"RSA\",\n" - + " \"e\":\"AQAB\",\n" - + " \"use\":\"sig\",\n" - + " \"kid\":\"one\",\n" - + " \"n\":\"0IUjrPZDz-3z0UE4ppcKU36v7hnh8FJjhu3lbJYj0qj9eZiwEJxi9HHUfSK1DhUQG7mJBbYTK1tPYCgre5EkfKh-64VhYUa-vz17zYCmuB8fFj4XHE3MLkWIG-AUn8hNbPzYYmiBTjfGnMKxLHjsbdTiF4mtn-85w366916R6midnAuiPD4HjZaZ1PAsuY60gr8bhMEDtJ8unz81hoQrozpBZJ6r8aR1PrsWb1OqPMloK9kAIutJNvWYKacp8WYAp2WWy72PxQ7Fb0eIA1br3A5dnp-Cln6JROJcZUIRJ-QvS6QONWeS2407uQmS-i-lybsqaH0ldYC7NBEBA5inPQ\"\n" - + " }\n" - + " ]\n" - + "}\n"; - // @formatter:on - - private Jwt jwt = TestJwts.jwt().build(); - - private String clientId = "client"; - - private String clientSecret = "secret"; - - // @formatter:off - private String active = "{\n" - + " \"active\": true,\n" - + " \"client_id\": \"l238j323ds-23ij4\",\n" - + " \"username\": \"jdoe\",\n" - + " \"scope\": \"read write dolphin\",\n" - + " \"sub\": \"Z5O3upPC88QrAjx00dis\",\n" - + " \"aud\": \"https://protected.example.net/resource\",\n" - + " \"iss\": \"https://server.example.com/\",\n" - + " \"exp\": 1419356238,\n" - + " \"iat\": 1419350238,\n" - + " \"extension_field\": \"twenty-seven\"\n" - + " }"; - // @formatter:on - - public final SpringTestContext spring = new SpringTestContext(this); - - WebTestClient client; - - @Autowired - public void setApplicationContext(ApplicationContext context) { - this.client = WebTestClient.bindToApplicationContext(context).build(); - } - - @Test - public void getWhenValidThenReturnsOk() { - this.spring.register(PublicKeyConfig.class, RootController.class).autowire(); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth(this.messageReadToken)) - .exchange() - .expectStatus().isOk(); - // @formatter:on - } - - @Test - public void getWhenExpiredThenReturnsInvalidToken() { - this.spring.register(PublicKeyConfig.class).autowire(); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth(this.expired)) - .exchange() - .expectStatus().isUnauthorized() - .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"invalid_token\"")); - // @formatter:on - } - - @Test - public void getWhenUnsignedThenReturnsInvalidToken() { - this.spring.register(PublicKeyConfig.class).autowire(); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth(this.unsignedToken)) - .exchange() - .expectStatus().isUnauthorized() - .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"invalid_token\"")); - // @formatter:on - } - - @Test - public void getWhenEmptyBearerTokenThenReturnsInvalidToken() { - this.spring.register(PublicKeyConfig.class).autowire(); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .add("Authorization", "Bearer ") - ) - .exchange() - .expectStatus().isUnauthorized() - .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"invalid_token\"")); - // @formatter:on - } - - @Test - public void getWhenValidTokenAndPublicKeyInLambdaThenReturnsOk() { - this.spring.register(PublicKeyInLambdaConfig.class, RootController.class).autowire(); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth(this.messageReadToken) - ) - .exchange() - .expectStatus().isOk(); - // @formatter:on - } - - @Test - public void getWhenExpiredTokenAndPublicKeyInLambdaThenReturnsInvalidToken() { - this.spring.register(PublicKeyInLambdaConfig.class).autowire(); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth(this.expired) - ) - .exchange() - .expectStatus().isUnauthorized() - .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"invalid_token\"")); - // @formatter:on - } - - @Test - public void getWhenValidUsingPlaceholderThenReturnsOk() { - this.spring.register(PlaceholderConfig.class, RootController.class).autowire(); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth(this.messageReadToken) - ) - .exchange() - .expectStatus().isOk(); - // @formatter:on - } - - @Test - public void getWhenCustomDecoderThenAuthenticatesAccordingly() { - this.spring.register(CustomDecoderConfig.class, RootController.class).autowire(); - ReactiveJwtDecoder jwtDecoder = this.spring.getContext().getBean(ReactiveJwtDecoder.class); - given(jwtDecoder.decode(anyString())).willReturn(Mono.just(this.jwt)); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth("token") - ) - .exchange() - .expectStatus().isOk(); - // @formatter:on - verify(jwtDecoder).decode(anyString()); - } - - @Test - public void getWhenUsingJwkSetUriThenConsultsAccordingly() { - this.spring.register(JwkSetUriConfig.class, RootController.class).autowire(); - MockWebServer mockWebServer = this.spring.getContext().getBean(MockWebServer.class); - mockWebServer.enqueue(new MockResponse().setBody(this.jwkSet)); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth(this.messageReadTokenWithKid) - ) - .exchange() - .expectStatus().isOk(); - // @formatter:on - } - - @Test - public void getWhenUsingJwkSetUriInLambdaThenConsultsAccordingly() { - this.spring.register(JwkSetUriInLambdaConfig.class, RootController.class).autowire(); - MockWebServer mockWebServer = this.spring.getContext().getBean(MockWebServer.class); - mockWebServer.enqueue(new MockResponse().setBody(this.jwkSet)); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth(this.messageReadTokenWithKid) - ) - .exchange() - .expectStatus().isOk(); - // @formatter:on - } - - @Test - public void getWhenUsingCustomAuthenticationManagerThenUsesItAccordingly() { - this.spring.register(CustomAuthenticationManagerConfig.class).autowire(); - ReactiveAuthenticationManager authenticationManager = this.spring.getContext() - .getBean(ReactiveAuthenticationManager.class); - given(authenticationManager.authenticate(any(Authentication.class))) - .willReturn(Mono.error(new OAuth2AuthenticationException(new OAuth2Error("mock-failure")))); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth(this.messageReadToken) - ) - .exchange() - .expectStatus().isUnauthorized() - .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"mock-failure\"")); - // @formatter:on - } - - @Test - public void getWhenUsingCustomAuthenticationManagerInLambdaThenUsesItAccordingly() { - this.spring.register(CustomAuthenticationManagerInLambdaConfig.class).autowire(); - ReactiveAuthenticationManager authenticationManager = this.spring.getContext() - .getBean(ReactiveAuthenticationManager.class); - given(authenticationManager.authenticate(any(Authentication.class))) - .willReturn(Mono.error(new OAuth2AuthenticationException(new OAuth2Error("mock-failure")))); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth(this.messageReadToken) - ) - .exchange() - .expectStatus().isUnauthorized() - .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"mock-failure\"")); - // @formatter:on - } - - @Test - public void getWhenUsingCustomAuthenticationManagerResolverThenUsesItAccordingly() { - this.spring.register(CustomAuthenticationManagerResolverConfig.class).autowire(); - ReactiveAuthenticationManagerResolver authenticationManagerResolver = this.spring - .getContext() - .getBean(ReactiveAuthenticationManagerResolver.class); - ReactiveAuthenticationManager authenticationManager = this.spring.getContext() - .getBean(ReactiveAuthenticationManager.class); - given(authenticationManagerResolver.resolve(any(ServerWebExchange.class))) - .willReturn(Mono.just(authenticationManager)); - given(authenticationManager.authenticate(any(Authentication.class))) - .willReturn(Mono.error(new OAuth2AuthenticationException(new OAuth2Error("mock-failure")))); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth(this.messageReadToken) - ) - .exchange() - .expectStatus().isUnauthorized() - .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"mock-failure\"")); - // @formatter:on - } - - @Test - public void getWhenUsingCustomAuthenticationFailureHandlerThenUsesIsAccordingly() { - this.spring.register(CustomAuthenticationFailureHandlerConfig.class).autowire(); - ServerAuthenticationFailureHandler handler = this.spring.getContext() - .getBean(ServerAuthenticationFailureHandler.class); - ReactiveAuthenticationManager authenticationManager = this.spring.getContext() - .getBean(ReactiveAuthenticationManager.class); - given(authenticationManager.authenticate(any())) - .willReturn(Mono.error(() -> new BadCredentialsException("bad"))); - given(handler.onAuthenticationFailure(any(), any())).willReturn(Mono.empty()); - // @formatter:off - this.client.get() - .headers((headers) -> headers.setBearerAuth(this.messageReadToken)) - .exchange() - .expectStatus().isOk(); - // @formatter:on - verify(handler).onAuthenticationFailure(any(), any()); - } - - @Test - public void postWhenSignedThenReturnsOk() { - this.spring.register(PublicKeyConfig.class, RootController.class).autowire(); - // @formatter:off - this.client.post() - .headers((headers) -> headers - .setBearerAuth(this.messageReadToken) - ) - .exchange() - .expectStatus().isOk(); - // @formatter:on - } - - @Test - public void getWhenTokenHasInsufficientScopeThenReturnsInsufficientScope() { - this.spring.register(DenyAllConfig.class, RootController.class).autowire(); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth(this.messageReadToken) - ) - .exchange() - .expectStatus().isForbidden() - .expectHeader().value(HttpHeaders.WWW_AUTHENTICATE, startsWith("Bearer error=\"insufficient_scope\"")); - // @formatter:on - } - - @Test - public void postWhenMissingTokenThenReturnsForbidden() { - this.spring.register(PublicKeyConfig.class, RootController.class).autowire(); - // @formatter:off - this.client.post() - .exchange() - .expectStatus().isForbidden(); - // @formatter:on - } - - @Test - public void getWhenCustomBearerTokenServerAuthenticationConverterThenResponds() { - this.spring.register(CustomBearerTokenServerAuthenticationConverter.class, RootController.class).autowire(); - // @formatter:off - this.client.get() - .cookie("TOKEN", this.messageReadToken) - .exchange() - .expectStatus().isOk(); - // @formatter:on - } - - @Test - public void getWhenSignedAndCustomConverterThenConverts() { - this.spring.register(CustomJwtAuthenticationConverterConfig.class, RootController.class).autowire(); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth(this.messageReadToken) - ) - .exchange() - .expectStatus().isOk(); - // @formatter:on - } - - @Test - public void getWhenCustomBearerTokenEntryPointThenResponds() { - this.spring.register(CustomErrorHandlingConfig.class).autowire(); - // @formatter:off - this.client.get() - .uri("/authenticated") - .exchange() - .expectStatus().isEqualTo(HttpStatus.I_AM_A_TEAPOT); - // @formatter:on - } - - @Test - public void getWhenCustomBearerTokenDeniedHandlerThenResponds() { - this.spring.register(CustomErrorHandlingConfig.class).autowire(); - // @formatter:off - this.client.get() - .uri("/unobtainable") - .headers((headers) -> headers - .setBearerAuth(this.messageReadToken) - ) - .exchange() - .expectStatus().isEqualTo(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED); - // @formatter:on - } - - @Test - public void getJwtDecoderWhenBeanWiredAndDslWiredThenDslTakesPrecedence() { - GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); - ServerHttpSecurity http = new ServerHttpSecurity(); - http.setApplicationContext(context); - ReactiveJwtDecoder beanWiredJwtDecoder = mock(ReactiveJwtDecoder.class); - ReactiveJwtDecoder dslWiredJwtDecoder = mock(ReactiveJwtDecoder.class); - context.registerBean(ReactiveJwtDecoder.class, () -> beanWiredJwtDecoder); - http.oauth2ResourceServer((server) -> server.jwt((jwt) -> { - jwt.jwtDecoder(dslWiredJwtDecoder); - assertThat(jwt.getJwtDecoder()).isEqualTo(dslWiredJwtDecoder); - })); - } - - @Test - public void getJwtDecoderWhenTwoBeansWiredAndDslWiredThenDslTakesPrecedence() { - GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); - ServerHttpSecurity http = new ServerHttpSecurity(); - http.setApplicationContext(context); - ReactiveJwtDecoder beanWiredJwtDecoder = mock(ReactiveJwtDecoder.class); - ReactiveJwtDecoder dslWiredJwtDecoder = mock(ReactiveJwtDecoder.class); - context.registerBean("firstJwtDecoder", ReactiveJwtDecoder.class, () -> beanWiredJwtDecoder); - context.registerBean("secondJwtDecoder", ReactiveJwtDecoder.class, () -> beanWiredJwtDecoder); - http.oauth2ResourceServer((server) -> server.jwt((jwt) -> { - jwt.jwtDecoder(dslWiredJwtDecoder); - assertThat(jwt.getJwtDecoder()).isEqualTo(dslWiredJwtDecoder); - })); - } - - @Test - public void getJwtDecoderWhenTwoBeansWiredThenThrowsWiringException() { - GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); - ServerHttpSecurity http = new ServerHttpSecurity(); - http.setApplicationContext(context); - ReactiveJwtDecoder beanWiredJwtDecoder = mock(ReactiveJwtDecoder.class); - context.registerBean("firstJwtDecoder", ReactiveJwtDecoder.class, () -> beanWiredJwtDecoder); - context.registerBean("secondJwtDecoder", ReactiveJwtDecoder.class, () -> beanWiredJwtDecoder); - http.oauth2ResourceServer( - (server) -> server.jwt((jwt) -> assertThatExceptionOfType(NoUniqueBeanDefinitionException.class) - .isThrownBy(jwt::getJwtDecoder))); - } - - @Test - public void getJwtDecoderWhenNoBeansAndNoDslWiredThenWiringException() { - GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); - ServerHttpSecurity http = new ServerHttpSecurity(); - http.setApplicationContext(context); - http.oauth2ResourceServer( - (server) -> server.jwt((jwt) -> assertThatExceptionOfType(NoSuchBeanDefinitionException.class) - .isThrownBy(jwt::getJwtDecoder))); - } - - @Test - public void getJwtAuthenticationConverterWhenBeanWiredAndDslWiredThenDslTakesPrecedence() { - GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); - ServerHttpSecurity http = new ServerHttpSecurity(); - http.setApplicationContext(context); - ReactiveJwtAuthenticationConverter beanWiredJwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); - ReactiveJwtAuthenticationConverter dslWiredJwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); - context.registerBean(ReactiveJwtAuthenticationConverter.class, () -> beanWiredJwtAuthenticationConverter); - http.oauth2ResourceServer((server) -> server.jwt((jwt) -> { - jwt.jwtAuthenticationConverter(dslWiredJwtAuthenticationConverter); - assertThat(jwt.getJwtAuthenticationConverter()).isEqualTo(dslWiredJwtAuthenticationConverter); - })); - } - - @Test - public void getJwtAuthenticationConverterWhenTwoBeansWiredAndDslWiredThenDslTakesPrecedence() { - GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); - ServerHttpSecurity http = new ServerHttpSecurity(); - http.setApplicationContext(context); - ReactiveJwtAuthenticationConverter beanWiredJwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); - ReactiveJwtAuthenticationConverter dslWiredJwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); - context.registerBean("firstJwtAuthenticationConverter", ReactiveJwtAuthenticationConverter.class, - () -> beanWiredJwtAuthenticationConverter); - context.registerBean("secondJwtAuthenticationConverter", ReactiveJwtAuthenticationConverter.class, - () -> beanWiredJwtAuthenticationConverter); - http.oauth2ResourceServer((server) -> server.jwt((jwt) -> { - jwt.jwtAuthenticationConverter(dslWiredJwtAuthenticationConverter); - assertThat(jwt.getJwtAuthenticationConverter()).isEqualTo(dslWiredJwtAuthenticationConverter); - })); - } - - @Test - public void getJwtAuthenticationConverterWhenTwoBeansWiredThenThrowsWiringException() { - GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); - ServerHttpSecurity http = new ServerHttpSecurity(); - http.setApplicationContext(context); - ReactiveJwtAuthenticationConverter beanWiredJwtAuthenticationConverter = new ReactiveJwtAuthenticationConverter(); - context.registerBean("firstJwtAuthenticationConverter", ReactiveJwtAuthenticationConverter.class, - () -> beanWiredJwtAuthenticationConverter); - context.registerBean("secondJwtAuthenticationConverter", ReactiveJwtAuthenticationConverter.class, - () -> beanWiredJwtAuthenticationConverter); - http.oauth2ResourceServer( - (server) -> server.jwt((jwt) -> assertThatExceptionOfType(NoUniqueBeanDefinitionException.class) - .isThrownBy(jwt::getJwtAuthenticationConverter))); - } - - @Test - public void getJwtAuthenticationConverterWhenNoBeansAndNoDslWiredThenDefaultConverter() { - GenericWebApplicationContext context = autowireWebServerGenericWebApplicationContext(); - ServerHttpSecurity http = new ServerHttpSecurity(); - http.setApplicationContext(context); - http.oauth2ResourceServer((server) -> server.jwt((jwt) -> assertThat(jwt.getJwtAuthenticationConverter()) - .isInstanceOf(ReactiveJwtAuthenticationConverter.class))); - } - - @Test - public void introspectWhenValidThenReturnsOk() { - this.spring.register(IntrospectionConfig.class, RootController.class).autowire(); - this.spring.getContext() - .getBean(MockWebServer.class) - .setDispatcher(requiresAuth(this.clientId, this.clientSecret, this.active)); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth(this.messageReadToken) - ) - .exchange() - .expectStatus().isOk(); - // @formatter:on - } - - @Test - public void introspectWhenValidAndIntrospectionInLambdaThenReturnsOk() { - this.spring.register(IntrospectionInLambdaConfig.class, RootController.class).autowire(); - this.spring.getContext() - .getBean(MockWebServer.class) - .setDispatcher(requiresAuth(this.clientId, this.clientSecret, this.active)); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth(this.messageReadToken) - ) - .exchange() - .expectStatus().isOk(); - // @formatter:on - } - - @Test - public void configureWhenUsingBothAuthenticationManagerResolverAndOpaqueThenWiringException() { - assertThatExceptionOfType(BeanCreationException.class) - .isThrownBy(() -> this.spring.register(AuthenticationManagerResolverPlusOtherConfig.class).autowire()) - .withMessageContaining("authenticationManagerResolver"); - } - - @Test - public void getWhenCustomAuthenticationConverterThenConverts() { - this.spring.register(ReactiveOpaqueTokenAuthenticationConverterConfig.class, RootController.class).autowire(); - this.spring.getContext() - .getBean(MockWebServer.class) - .setDispatcher(requiresAuth(this.clientId, this.clientSecret, this.active)); - ReactiveOpaqueTokenAuthenticationConverter authenticationConverter = this.spring.getContext() - .getBean(ReactiveOpaqueTokenAuthenticationConverter.class); - given(authenticationConverter.convert(anyString(), any(OAuth2AuthenticatedPrincipal.class))) - .willReturn(Mono.just(new TestingAuthenticationToken("jdoe", null, Collections.emptyList()))); - // @formatter:off - this.client.get() - .headers((headers) -> headers - .setBearerAuth(this.messageReadToken) - ) - .exchange() - .expectStatus().isOk(); - // @formatter:on - } - - private static Dispatcher requiresAuth(String username, String password, String response) { - return new Dispatcher() { - @Override - public MockResponse dispatch(RecordedRequest request) { - String authorization = request.getHeader(org.springframework.http.HttpHeaders.AUTHORIZATION); - // @formatter:off - return Optional.ofNullable(authorization) - .filter((a) -> isAuthorized(authorization, username, password)) - .map((a) -> ok(response)) - .orElse(unauthorized()); - // @formatter:on - } - }; - } - - private static boolean isAuthorized(String authorization, String username, String password) { - String[] values = new String(Base64.getDecoder().decode(authorization.substring(6))).split(":"); - return username.equals(values[0]) && password.equals(values[1]); - } - - private static MockResponse ok(String response) { - return new MockResponse().setBody(response) - .setHeader(org.springframework.http.HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); - } - - private static MockResponse unauthorized() { - return new MockResponse().setResponseCode(401); - } - - private static RSAPublicKey publicKey() { - String modulus = "26323220897278656456354815752829448539647589990395639665273015355787577386000316054335559633864476469390247312823732994485311378484154955583861993455004584140858982659817218753831620205191028763754231454775026027780771426040997832758235764611119743390612035457533732596799927628476322029280486807310749948064176545712270582940917249337311592011920620009965129181413510845780806191965771671528886508636605814099711121026468495328702234901200169245493126030184941412539949521815665744267183140084667383643755535107759061065656273783542590997725982989978433493861515415520051342321336460543070448417126615154138673620797"; - String exponent = "65537"; - RSAPublicKeySpec spec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(exponent)); - RSAPublicKey rsaPublicKey = null; - try { - KeyFactory factory = KeyFactory.getInstance("RSA"); - rsaPublicKey = (RSAPublicKey) factory.generatePublic(spec); - } - catch (NoSuchAlgorithmException | InvalidKeySpecException ex) { - ex.printStackTrace(); - } - return rsaPublicKey; - } - - private GenericWebApplicationContext autowireWebServerGenericWebApplicationContext() { - GenericWebApplicationContext context = new GenericWebApplicationContext(); - context.registerBean("webHandler", DispatcherHandler.class); - this.spring.context(context).autowire(); - return (GenericWebApplicationContext) this.spring.getContext(); - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class PublicKeyConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .anyExchange().hasAuthority("SCOPE_message:read")) - .oauth2ResourceServer((server) -> server - .jwt((jwt) -> jwt.publicKey(publicKey()))); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class PublicKeyInLambdaConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .anyExchange().hasAuthority("SCOPE_message:read") - ) - .oauth2ResourceServer((oauth2) -> oauth2 - .jwt((jwt) -> jwt - .publicKey(publicKey()) - ) - ); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class PlaceholderConfig { - - @Value("classpath:org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests-simple.pub") - RSAPublicKey key; - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .anyExchange().hasAuthority("SCOPE_message:read")) - .oauth2ResourceServer((server) -> server - .jwt((jwt) -> jwt.publicKey(this.key))); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class JwkSetUriConfig { - - private MockWebServer mockWebServer = new MockWebServer(); - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - String jwkSetUri = mockWebServer().url("/.well-known/jwks.json").toString(); - // @formatter:off - http - .oauth2ResourceServer((server) -> server - .jwt((jwt) -> jwt.jwkSetUri(jwkSetUri))); - // @formatter:on - return http.build(); - } - - @Bean - MockWebServer mockWebServer() { - return this.mockWebServer; - } - - @PreDestroy - void shutdown() throws IOException { - this.mockWebServer.shutdown(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class JwkSetUriInLambdaConfig { - - private MockWebServer mockWebServer = new MockWebServer(); - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - String jwkSetUri = mockWebServer().url("/.well-known/jwks.json").toString(); - // @formatter:off - http - .oauth2ResourceServer((oauth2) -> oauth2 - .jwt((jwt) -> jwt - .jwkSetUri(jwkSetUri) - ) - ); - // @formatter:on - return http.build(); - } - - @Bean - MockWebServer mockWebServer() { - return this.mockWebServer; - } - - @PreDestroy - void shutdown() throws IOException { - this.mockWebServer.shutdown(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class CustomDecoderConfig { - - ReactiveJwtDecoder jwtDecoder = mock(ReactiveJwtDecoder.class); - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .oauth2ResourceServer((server) -> server - .jwt(Customizer.withDefaults())); - // @formatter:on - return http.build(); - } - - @Bean - ReactiveJwtDecoder jwtDecoder() { - return this.jwtDecoder; - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class DenyAllConfig { - - @Bean - SecurityWebFilterChain authorization(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .anyExchange().denyAll()) - .oauth2ResourceServer((server) -> server - .jwt((jwt) -> jwt.publicKey(publicKey()))); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class CustomAuthenticationManagerConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .oauth2ResourceServer((server) -> server - .jwt((jwt) -> jwt.authenticationManager(authenticationManager()))); - // @formatter:on - return http.build(); - } - - @Bean - ReactiveAuthenticationManager authenticationManager() { - return mock(ReactiveAuthenticationManager.class); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class CustomAuthenticationManagerInLambdaConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .oauth2ResourceServer((oauth2) -> oauth2 - .jwt((jwt) -> jwt - .authenticationManager(authenticationManager()) - ) - ); - // @formatter:on - return http.build(); - } - - @Bean - ReactiveAuthenticationManager authenticationManager() { - return mock(ReactiveAuthenticationManager.class); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class CustomAuthenticationManagerResolverConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .pathMatchers("/*/message/**").hasAnyAuthority("SCOPE_message:read")) - .oauth2ResourceServer((server) -> server - .authenticationManagerResolver(authenticationManagerResolver())); - // @formatter:on - return http.build(); - } - - @Bean - ReactiveAuthenticationManagerResolver authenticationManagerResolver() { - return mock(ReactiveAuthenticationManagerResolver.class); - } - - @Bean - ReactiveAuthenticationManager authenticationManager() { - return mock(ReactiveAuthenticationManager.class); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class CustomAuthenticationFailureHandlerConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) - .oauth2ResourceServer((oauth2) -> oauth2 - .authenticationFailureHandler(authenticationFailureHandler()) - .jwt((jwt) -> jwt.authenticationManager(authenticationManager())) - ); - // @formatter:on - return http.build(); - } - - @Bean - ReactiveAuthenticationManager authenticationManager() { - return mock(ReactiveAuthenticationManager.class); - } - - @Bean - ServerAuthenticationFailureHandler authenticationFailureHandler() { - return mock(ServerAuthenticationFailureHandler.class); - } - - } - - @EnableWebFlux - @EnableWebFluxSecurity - static class CustomBearerTokenServerAuthenticationConverter { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .anyExchange().hasAuthority("SCOPE_message:read")) - .oauth2ResourceServer((server) -> server - .bearerTokenConverter(bearerTokenAuthenticationConverter()) - .jwt((jwt) -> jwt.publicKey(publicKey()))); - // @formatter:on - return http.build(); - } - - @Bean - ServerAuthenticationConverter bearerTokenAuthenticationConverter() { - return (exchange) -> Mono.justOrEmpty(exchange.getRequest().getCookies().getFirst("TOKEN").getValue()) - .map(BearerTokenAuthenticationToken::new); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class CustomJwtAuthenticationConverterConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .anyExchange().hasAuthority("message:read")) - .oauth2ResourceServer((server) -> server - .jwt((jwt) -> jwt - .jwtAuthenticationConverter(jwtAuthenticationConverter()) - .publicKey(publicKey()))); - // @formatter:on - return http.build(); - } - - @Bean - Converter> jwtAuthenticationConverter() { - JwtAuthenticationConverter converter = new JwtAuthenticationConverter(); - converter.setJwtGrantedAuthoritiesConverter((jwt) -> { - String[] claims = ((String) jwt.getClaims().get("scope")).split(" "); - return Stream.of(claims).map(SimpleGrantedAuthority::new).collect(Collectors.toList()); - }); - return new ReactiveJwtAuthenticationConverterAdapter(converter); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class CustomErrorHandlingConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .pathMatchers("/authenticated").authenticated() - .pathMatchers("/unobtainable").hasAuthority("unobtainable")) - .oauth2ResourceServer((server) -> server - .accessDeniedHandler(new HttpStatusServerAccessDeniedHandler(HttpStatus.BANDWIDTH_LIMIT_EXCEEDED)) - .authenticationEntryPoint(new HttpStatusServerEntryPoint(HttpStatus.I_AM_A_TEAPOT)) - .jwt((jwt) -> jwt.publicKey(publicKey()))); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class IntrospectionConfig { - - private MockWebServer mockWebServer = new MockWebServer(); - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - String introspectionUri = mockWebServer().url("/introspect").toString(); - // @formatter:off - http - .oauth2ResourceServer((server) -> server - .opaqueToken((opaqueToken) -> opaqueToken - .introspectionUri(introspectionUri) - .introspectionClientCredentials("client", "secret"))); - // @formatter:on - return http.build(); - } - - @Bean - MockWebServer mockWebServer() { - return this.mockWebServer; - } - - @PreDestroy - void shutdown() throws IOException { - this.mockWebServer.shutdown(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class IntrospectionInLambdaConfig { - - private MockWebServer mockWebServer = new MockWebServer(); - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - String introspectionUri = mockWebServer().url("/introspect").toString(); - // @formatter:off - http - .oauth2ResourceServer((oauth2) -> oauth2 - .opaqueToken((opaqueToken) -> opaqueToken - .introspectionUri(introspectionUri) - .introspectionClientCredentials("client", "secret") - ) - ); - // @formatter:on - return http.build(); - } - - @Bean - MockWebServer mockWebServer() { - return this.mockWebServer; - } - - @PreDestroy - void shutdown() throws IOException { - this.mockWebServer.shutdown(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class AuthenticationManagerResolverPlusOtherConfig { - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize - .anyExchange().authenticated()) - .oauth2ResourceServer((server) -> server - .authenticationManagerResolver(mock(ReactiveAuthenticationManagerResolver.class)) - .opaqueToken(Customizer.withDefaults())); - // @formatter:on - return http.build(); - } - - } - - @Configuration - @EnableWebFlux - @EnableWebFluxSecurity - static class ReactiveOpaqueTokenAuthenticationConverterConfig { - - private MockWebServer mockWebServer = new MockWebServer(); - - @Bean - SecurityWebFilterChain springSecurity(ServerHttpSecurity http) { - String introspectionUri = mockWebServer().url("/introspect").toString(); - // @formatter:off - http - .oauth2ResourceServer((server) -> server - .opaqueToken((opaqueToken) -> opaqueToken - .introspectionUri(introspectionUri) - .introspectionClientCredentials("client", "secret") - .authenticationConverter(authenticationConverter()))); - // @formatter:on - return http.build(); - } - - @Bean - ReactiveOpaqueTokenAuthenticationConverter authenticationConverter() { - return mock(ReactiveOpaqueTokenAuthenticationConverter.class); - } - - @Bean - MockWebServer mockWebServer() { - return this.mockWebServer; - } - - @PreDestroy - void shutdown() throws IOException { - this.mockWebServer.shutdown(); - } - - } - - @RestController - static class RootController { - - @GetMapping - Mono get() { - return Mono.just("ok"); - } - - @PostMapping - Mono post() { - return Mono.just("ok"); - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/web/server/OidcBackChannelServerLogoutHandlerTests.java b/config/src/test/java/org/springframework/security/config/web/server/OidcBackChannelServerLogoutHandlerTests.java deleted file mode 100644 index bffcb976ef8..00000000000 --- a/config/src/test/java/org/springframework/security/config/web/server/OidcBackChannelServerLogoutHandlerTests.java +++ /dev/null @@ -1,73 +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.web.server; - -import org.junit.jupiter.api.Test; - -import org.springframework.mock.http.server.reactive.MockServerHttpRequest; -import org.springframework.security.oauth2.client.oidc.authentication.logout.TestOidcLogoutTokens; -import org.springframework.security.oauth2.client.oidc.server.session.InMemoryReactiveOidcSessionRegistry; -import org.springframework.security.oauth2.client.oidc.server.session.ReactiveOidcSessionRegistry; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Tests for {@link OidcBackChannelServerLogoutHandler} - */ -public class OidcBackChannelServerLogoutHandlerTests { - - private final ReactiveOidcSessionRegistry sessionRegistry = new InMemoryReactiveOidcSessionRegistry(); - - private final OidcBackChannelLogoutAuthentication token = new OidcBackChannelLogoutAuthentication( - TestOidcLogoutTokens.withSubject("issuer", "subject").build(), - TestClientRegistrations.clientRegistration().build()); - - // gh-14553 - @Test - public void computeLogoutEndpointWhenDifferentHostnameThenLocalhost() { - OidcBackChannelServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler(this.sessionRegistry); - logoutHandler.setLogoutUri("{baseScheme}://localhost{basePort}/logout"); - MockServerHttpRequest request = MockServerHttpRequest - .get("https://host.docker.internal:8090/back-channel/logout") - .build(); - String endpoint = logoutHandler.computeLogoutEndpoint(request, this.token); - assertThat(endpoint).startsWith("https://localhost:8090/logout"); - } - - @Test - public void computeLogoutEndpointWhenUsingBaseUrlTemplateThenServerName() { - OidcBackChannelServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler(this.sessionRegistry); - logoutHandler.setLogoutUri("{baseUrl}/logout"); - MockServerHttpRequest request = MockServerHttpRequest - .get("http://host.docker.internal:8090/back-channel/logout") - .build(); - String endpoint = logoutHandler.computeLogoutEndpoint(request, this.token); - assertThat(endpoint).startsWith("http://host.docker.internal:8090/logout"); - } - - // gh-14609 - @Test - public void computeLogoutEndpointWhenLogoutUriThenUses() { - OidcBackChannelServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler(this.sessionRegistry); - logoutHandler.setLogoutUri("http://localhost:8090/logout"); - MockServerHttpRequest request = MockServerHttpRequest.get("https://server-one.com/back-channel/logout").build(); - String endpoint = logoutHandler.computeLogoutEndpoint(request, this.token); - assertThat(endpoint).startsWith("http://localhost:8090/logout"); - } - -} diff --git a/config/src/test/java/org/springframework/security/config/web/server/OidcLogoutSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/OidcLogoutSpecTests.java deleted file mode 100644 index 13afcf091a0..00000000000 --- a/config/src/test/java/org/springframework/security/config/web/server/OidcLogoutSpecTests.java +++ /dev/null @@ -1,960 +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.web.server; - -import java.io.IOException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.interfaces.RSAPublicKey; -import java.time.Duration; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jose.jwk.source.ImmutableJWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.proc.SecurityContext; -import com.nimbusds.oauth2.sdk.Scope; -import com.nimbusds.oauth2.sdk.token.BearerAccessToken; -import com.nimbusds.openid.connect.sdk.token.OIDCTokens; -import jakarta.annotation.PreDestroy; -import okhttp3.mockwebserver.Dispatcher; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import okhttp3.mockwebserver.RecordedRequest; -import org.htmlunit.util.UrlUtils; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.core.annotation.Order; -import org.springframework.http.ResponseCookie; -import org.springframework.http.client.reactive.ClientHttpConnector; -import org.springframework.security.authentication.TestingAuthenticationToken; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; -import org.springframework.security.core.userdetails.ReactiveUserDetailsService; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.oauth2.client.oidc.authentication.logout.LogoutTokenClaimNames; -import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken; -import org.springframework.security.oauth2.client.oidc.authentication.logout.TestOidcLogoutTokens; -import org.springframework.security.oauth2.client.oidc.server.session.InMemoryReactiveOidcSessionRegistry; -import org.springframework.security.oauth2.client.oidc.server.session.ReactiveOidcSessionRegistry; -import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; -import org.springframework.security.oauth2.client.registration.TestClientRegistrations; -import org.springframework.security.oauth2.core.oidc.OidcIdToken; -import org.springframework.security.oauth2.core.oidc.TestOidcIdTokens; -import org.springframework.security.oauth2.core.oidc.user.OidcUser; -import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; -import org.springframework.security.oauth2.jwt.JwsHeader; -import org.springframework.security.oauth2.jwt.JwtClaimsSet; -import org.springframework.security.oauth2.jwt.JwtEncoder; -import org.springframework.security.oauth2.jwt.JwtEncoderParameters; -import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler; -import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher; -import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher; -import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; -import org.springframework.test.web.reactive.server.FluxExchangeResult; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.test.web.reactive.server.WebTestClientConfigurer; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.context.ConfigurableWebApplicationContext; -import org.springframework.web.reactive.config.EnableWebFlux; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.server.WebSession; -import org.springframework.web.server.adapter.WebHttpHandlerBuilder; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.hasValue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockAuthentication; -import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; - -/** - * Tests for {@link ServerHttpSecurity.OAuth2ResourceServerSpec} - */ -@ExtendWith({ SpringTestContextExtension.class }) -public class OidcLogoutSpecTests { - - private static final String SESSION_COOKIE_NAME = "SESSION"; - - private WebTestClient test; - - @Autowired(required = false) - private MockWebServer web; - - @Autowired - private ClientRegistration clientRegistration; - - public final SpringTestContext spring = new SpringTestContext(this); - - @Autowired - public void setApplicationContext(ApplicationContext context) { - this.test = WebTestClient.bindToApplicationContext(context) - .apply(springSecurity()) - .configureClient() - .responseTimeout(Duration.ofDays(1)) - .build(); - if (context instanceof ConfigurableWebApplicationContext configurable) { - configurable.getBeanFactory().registerResolvableDependency(WebTestClient.class, this.test); - } - } - - @Test - void logoutWhenDefaultsThenRemotelyInvalidatesSessions() { - this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire(); - String registrationId = this.clientRegistration.getRegistrationId(); - String session = login(); - String logoutToken = this.test.mutateWith(session(session)) - .get() - .uri("/token/logout") - .exchange() - .expectStatus() - .isOk() - .returnResult(String.class) - .getResponseBody() - .blockFirst(); - this.test.post() - .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .body(BodyInserters.fromFormData("logout_token", logoutToken)) - .exchange() - .expectStatus() - .isOk(); - this.test.mutateWith(session(session)).get().uri("/token/logout").exchange().expectStatus().isUnauthorized(); - } - - @Test - void logoutWhenInvalidLogoutTokenThenBadRequest() { - this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire(); - this.test.get().uri("/token/logout").exchange().expectStatus().isUnauthorized(); - String registrationId = this.clientRegistration.getRegistrationId(); - FluxExchangeResult result = this.test.get() - .uri("/oauth2/authorization/" + registrationId) - .exchange() - .expectStatus() - .isFound() - .returnResult(String.class); - String session = sessionId(result); - String redirectUrl = UrlUtils.decode(result.getResponseHeaders().getLocation().toString()); - String state = this.test - .mutateWith(mockAuthentication(new TestingAuthenticationToken(this.clientRegistration.getClientId(), - this.clientRegistration.getClientSecret(), "APP"))) - .get() - .uri(redirectUrl) - .exchange() - .returnResult(String.class) - .getResponseBody() - .blockFirst(); - result = this.test.get() - .uri("/login/oauth2/code/" + registrationId + "?code=code&state=" + state) - .cookie("SESSION", session) - .exchange() - .expectStatus() - .isFound() - .returnResult(String.class); - session = sessionId(result); - this.test.post() - .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .body(BodyInserters.fromFormData("logout_token", "invalid")) - .exchange() - .expectStatus() - .isBadRequest() - .expectBody(new ParameterizedTypeReference>() { - }) - .value(hasValue("invalid_request")); - this.test.get().uri("/token/logout").cookie("SESSION", session).exchange().expectStatus().isOk(); - } - - @Test - void logoutWhenLogoutTokenSpecifiesOneSessionThenRemotelyInvalidatesOnlyThatSession() throws Exception { - this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire(); - String registrationId = this.clientRegistration.getRegistrationId(); - String one = login(); - String two = login(); - String three = login(); - String logoutToken = this.test.get() - .uri("/token/logout") - .cookie("SESSION", one) - .exchange() - .expectStatus() - .isOk() - .returnResult(String.class) - .getResponseBody() - .blockFirst(); - this.test.post() - .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .body(BodyInserters.fromFormData("logout_token", logoutToken)) - .exchange() - .expectStatus() - .isOk(); - this.test.get().uri("/token/logout").cookie("SESSION", one).exchange().expectStatus().isUnauthorized(); - this.test.get().uri("/token/logout").cookie("SESSION", two).exchange().expectStatus().isOk(); - logoutToken = this.test.get() - .uri("/token/logout/all") - .cookie("SESSION", three) - .exchange() - .expectStatus() - .isOk() - .returnResult(String.class) - .getResponseBody() - .blockFirst(); - this.test.post() - .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .body(BodyInserters.fromFormData("logout_token", logoutToken)) - .exchange() - .expectStatus() - .isOk(); - this.test.get().uri("/token/logout").cookie("SESSION", two).exchange().expectStatus().isUnauthorized(); - this.test.get().uri("/token/logout").cookie("SESSION", three).exchange().expectStatus().isUnauthorized(); - } - - @Test - void logoutWhenRemoteLogoutUriThenUses() { - this.spring.register(WebServerConfig.class, OidcProviderConfig.class, LogoutUriConfig.class).autowire(); - String registrationId = this.clientRegistration.getRegistrationId(); - String one = login(); - String logoutToken = this.test.get() - .uri("/token/logout/all") - .cookie("SESSION", one) - .exchange() - .expectStatus() - .isOk() - .returnResult(String.class) - .getResponseBody() - .blockFirst(); - this.test.post() - .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .body(BodyInserters.fromFormData("logout_token", logoutToken)) - .exchange() - .expectStatus() - .isBadRequest() - .expectBody(new ParameterizedTypeReference>() { - }) - .value(hasValue("partial_logout")) - .value(hasValue(containsString("not all sessions were terminated"))); - this.test.get().uri("/token/logout").cookie("SESSION", one).exchange().expectStatus().isOk(); - } - - @Test - void logoutWhenSelfRemoteLogoutUriThenUses() { - this.spring.register(WebServerConfig.class, OidcProviderConfig.class, SelfLogoutUriConfig.class).autowire(); - String registrationId = this.clientRegistration.getRegistrationId(); - String sessionId = login(); - String logoutToken = this.test.get() - .uri("/token/logout") - .cookie("SESSION", sessionId) - .exchange() - .expectStatus() - .isOk() - .returnResult(String.class) - .getResponseBody() - .blockFirst(); - this.test.post() - .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .body(BodyInserters.fromFormData("logout_token", logoutToken)) - .exchange() - .expectStatus() - .isOk(); - this.test.get().uri("/token/logout").cookie("SESSION", sessionId).exchange().expectStatus().isUnauthorized(); - } - - @Test - void logoutWhenDifferentCookieNameThenUses() { - this.spring.register(OidcProviderConfig.class, CookieConfig.class).autowire(); - String registrationId = this.clientRegistration.getRegistrationId(); - String sessionId = login(); - String logoutToken = this.test.get() - .uri("/token/logout") - .cookie("SESSION", sessionId) - .exchange() - .expectStatus() - .isOk() - .returnResult(String.class) - .getResponseBody() - .blockFirst(); - this.test.post() - .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .body(BodyInserters.fromFormData("logout_token", logoutToken)) - .exchange() - .expectStatus() - .isOk(); - this.test.get().uri("/token/logout").cookie("SESSION", sessionId).exchange().expectStatus().isUnauthorized(); - } - - @Test - void logoutWhenRemoteLogoutFailsThenReportsPartialLogout() { - this.spring.register(WebServerConfig.class, OidcProviderConfig.class, WithBrokenLogoutConfig.class).autowire(); - ServerLogoutHandler logoutHandler = this.spring.getContext().getBean(ServerLogoutHandler.class); - given(logoutHandler.logout(any(), any())).willReturn(Mono.error(() -> new IllegalStateException("illegal"))); - String registrationId = this.clientRegistration.getRegistrationId(); - String one = login(); - String logoutToken = this.test.get() - .uri("/token/logout/all") - .cookie("SESSION", one) - .exchange() - .expectStatus() - .isOk() - .returnResult(String.class) - .getResponseBody() - .blockFirst(); - this.test.post() - .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .body(BodyInserters.fromFormData("logout_token", logoutToken)) - .exchange() - .expectStatus() - .isBadRequest() - .expectBody(String.class) - .value(containsString("partial_logout")); - this.test.get().uri("/token/logout").cookie("SESSION", one).exchange().expectStatus().isOk(); - } - - @Test - void logoutWhenCustomComponentsThenUses() { - this.spring.register(WebServerConfig.class, OidcProviderConfig.class, WithCustomComponentsConfig.class) - .autowire(); - String registrationId = this.clientRegistration.getRegistrationId(); - String sessionId = login(); - String logoutToken = this.test.get() - .uri("/token/logout") - .cookie("SESSION", sessionId) - .exchange() - .expectStatus() - .isOk() - .returnResult(String.class) - .getResponseBody() - .blockFirst(); - this.test.post() - .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .body(BodyInserters.fromFormData("logout_token", logoutToken)) - .exchange() - .expectStatus() - .isOk(); - this.test.get().uri("/token/logout").cookie("SESSION", sessionId).exchange().expectStatus().isUnauthorized(); - ReactiveOidcSessionRegistry sessionRegistry = this.spring.getContext() - .getBean(ReactiveOidcSessionRegistry.class); - verify(sessionRegistry, atLeastOnce()).saveSessionInformation(any()); - verify(sessionRegistry, atLeastOnce()).removeSessionInformation(any(OidcLogoutToken.class)); - } - - @Test - void logoutWhenProviderIssuerMissingThen5xxServerError() { - this.spring.register(WebServerConfig.class, OidcProviderConfig.class, ProviderIssuerMissingConfig.class) - .autowire(); - String registrationId = this.clientRegistration.getRegistrationId(); - String session = login(); - String logoutToken = this.test.mutateWith(session(session)) - .get() - .uri("/token/logout") - .exchange() - .expectStatus() - .isOk() - .returnResult(String.class) - .getResponseBody() - .blockFirst(); - this.test.post() - .uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString()) - .body(BodyInserters.fromFormData("logout_token", logoutToken)) - .exchange() - .expectStatus() - .is5xxServerError(); - this.test.mutateWith(session(session)).get().uri("/token/logout").exchange().expectStatus().isOk(); - } - - private String login() { - this.test.get().uri("/token/logout").exchange().expectStatus().isUnauthorized(); - String registrationId = this.clientRegistration.getRegistrationId(); - FluxExchangeResult result = this.test.get() - .uri("/oauth2/authorization/" + registrationId) - .exchange() - .expectStatus() - .isFound() - .returnResult(String.class); - String sessionId = sessionId(result); - String redirectUrl = UrlUtils.decode(result.getResponseHeaders().getLocation().toString()); - result = this.test - .mutateWith(mockAuthentication(new TestingAuthenticationToken(this.clientRegistration.getClientId(), - this.clientRegistration.getClientSecret(), "APP"))) - .get() - .uri(redirectUrl) - .exchange() - .returnResult(String.class); - String state = result.getResponseBody().blockFirst(); - result = this.test.mutateWith(session(sessionId)) - .get() - .uri("/login/oauth2/code/" + registrationId + "?code=code&state=" + state) - .exchange() - .expectStatus() - .isFound() - .returnResult(String.class); - return sessionId(result); - } - - private String sessionId(FluxExchangeResult result) { - List cookies = result.getResponseCookies().get(SESSION_COOKIE_NAME); - if (cookies == null || cookies.isEmpty()) { - return null; - } - return cookies.get(0).getValue(); - } - - static SessionMutator session(String session) { - return new SessionMutator(session); - } - - private record SessionMutator(String session) implements WebTestClientConfigurer { - - @Override - public void afterConfigurerAdded(WebTestClient.Builder builder, WebHttpHandlerBuilder httpHandlerBuilder, - ClientHttpConnector connector) { - builder.defaultCookie(SESSION_COOKIE_NAME, this.session); - } - - } - - @Configuration - static class RegistrationConfig { - - @Autowired(required = false) - MockWebServer web; - - @Bean - ClientRegistration clientRegistration() { - if (this.web == null) { - return TestClientRegistrations.clientRegistration().build(); - } - String issuer = this.web.url("/").toString(); - return TestClientRegistrations.clientRegistration() - .issuerUri(issuer) - .jwkSetUri(issuer + "jwks") - .tokenUri(issuer + "token") - .userInfoUri(issuer + "user") - .scope("openid") - .build(); - } - - @Bean - ReactiveClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) { - return new InMemoryReactiveClientRegistrationRepository(clientRegistration); - } - - } - - @Configuration - @EnableWebFluxSecurity - @Import(RegistrationConfig.class) - static class DefaultConfig { - - @Bean - @Order(1) - SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) - .oauth2Login(Customizer.withDefaults()) - .oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults())); - // @formatter:on - - return http.build(); - } - - } - - @Configuration - @EnableWebFluxSecurity - @Import(RegistrationConfig.class) - static class LogoutUriConfig { - - @Bean - @Order(1) - SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) - .oauth2Login(Customizer.withDefaults()) - .oidcLogout((oidc) -> oidc - .backChannel((backchannel) -> backchannel.logoutUri("http://localhost/wrong")) - ); - // @formatter:on - - return http.build(); - } - - } - - @Configuration - @EnableWebFluxSecurity - @Import(RegistrationConfig.class) - static class SelfLogoutUriConfig { - - @Bean - @Order(1) - SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) - .oauth2Login(Customizer.withDefaults()) - .oidcLogout((oidc) -> oidc - .backChannel(Customizer.withDefaults()) - ); - // @formatter:on - - return http.build(); - } - - } - - @Configuration - @EnableWebFluxSecurity - @Import(RegistrationConfig.class) - static class CookieConfig { - - private final MockWebServer server = new MockWebServer(); - - @Bean - @Order(1) - SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) - .oauth2Login(Customizer.withDefaults()) - .oidcLogout((oidc) -> oidc - .backChannel(Customizer.withDefaults()) - ); - // @formatter:on - - return http.build(); - } - - @Bean - ReactiveOidcSessionRegistry oidcSessionRegistry() { - return new InMemoryReactiveOidcSessionRegistry(); - } - - @Bean - OidcBackChannelServerLogoutHandler oidcLogoutHandler(ReactiveOidcSessionRegistry sessionRegistry) { - OidcBackChannelServerLogoutHandler logoutHandler = new OidcBackChannelServerLogoutHandler(sessionRegistry); - logoutHandler.setSessionCookieName("JSESSIONID"); - return logoutHandler; - } - - @Bean - MockWebServer web(ObjectProvider web) { - WebTestClientDispatcher dispatcher = new WebTestClientDispatcher(web); - dispatcher.setAssertion((rr) -> { - String cookie = rr.getHeaders().get("Cookie"); - if (cookie == null) { - return; - } - assertThat(cookie).contains("JSESSIONID"); - }); - this.server.setDispatcher(dispatcher); - return this.server; - } - - @PreDestroy - void shutdown() throws IOException { - this.server.shutdown(); - } - - } - - @Configuration - @EnableWebFluxSecurity - @Import(RegistrationConfig.class) - static class WithCustomComponentsConfig { - - ReactiveOidcSessionRegistry sessionRegistry = spy(new InMemoryReactiveOidcSessionRegistry()); - - @Bean - @Order(1) - SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) - .oauth2Login(Customizer.withDefaults()) - .oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults())); - // @formatter:on - - return http.build(); - } - - @Bean - ReactiveOidcSessionRegistry sessionRegistry() { - return this.sessionRegistry; - } - - } - - @Configuration - @EnableWebFluxSecurity - @Import(RegistrationConfig.class) - static class WithBrokenLogoutConfig { - - private final ServerLogoutHandler logoutHandler = mock(ServerLogoutHandler.class); - - @Bean - @Order(1) - SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) - .logout((logout) -> logout.logoutHandler(this.logoutHandler)) - .oauth2Login(Customizer.withDefaults()) - .oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults())); - // @formatter:on - - return http.build(); - } - - @Bean - ServerLogoutHandler logoutHandler() { - return this.logoutHandler; - } - - } - - @Configuration - static class ProviderIssuerMissingRegistrationConfig { - - @Autowired(required = false) - MockWebServer web; - - @Bean - ClientRegistration clientRegistration() { - if (this.web == null) { - return TestClientRegistrations.clientRegistration().issuerUri(null).build(); - } - String issuer = this.web.url("/").toString(); - return TestClientRegistrations.clientRegistration() - .issuerUri(null) - .jwkSetUri(issuer + "jwks") - .tokenUri(issuer + "token") - .userInfoUri(issuer + "user") - .scope("openid") - .build(); - } - - @Bean - ReactiveClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) { - return new InMemoryReactiveClientRegistrationRepository(clientRegistration); - } - - } - - @Configuration - @EnableWebFluxSecurity - @Import(ProviderIssuerMissingRegistrationConfig.class) - static class ProviderIssuerMissingConfig { - - @Bean - @Order(1) - SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception { - // @formatter:off - http - .authorizeExchange((authorize) -> authorize.anyExchange().authenticated()) - .oauth2Login(Customizer.withDefaults()) - .oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults())); - // @formatter:on - - return http.build(); - } - - } - - @Configuration - @EnableWebFluxSecurity - @EnableWebFlux - @RestController - static class OidcProviderConfig { - - private static final RSAKey key = key(); - - private static final JWKSource jwks = jwks(key); - - private static RSAKey key() { - try { - KeyPair pair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); - return new RSAKey.Builder((RSAPublicKey) pair.getPublic()).privateKey(pair.getPrivate()).build(); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - private static JWKSource jwks(RSAKey key) { - try { - return new ImmutableJWKSet<>(new JWKSet(key)); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - private final String username = "user"; - - private final JwtEncoder encoder = new NimbusJwtEncoder(jwks); - - private String nonce; - - @Autowired - ClientRegistration registration; - - @Autowired(required = false) - MockWebServer web; - - static ServerWebExchangeMatcher or(String... patterns) { - List matchers = new ArrayList<>(); - for (String pattern : patterns) { - matchers.add(new PathPatternParserServerWebExchangeMatcher(pattern)); - } - return new OrServerWebExchangeMatcher(matchers); - } - - @Bean - @Order(0) - SecurityWebFilterChain authorizationServer(ServerHttpSecurity http, ClientRegistration registration) - throws Exception { - // @formatter:off - http - .securityMatcher(or("/jwks", "/login/oauth/authorize", "/nonce", "/token", "/token/logout", "/user")) - .authorizeExchange((authorize) -> authorize - .pathMatchers("/jwks").permitAll() - .anyExchange().authenticated() - ) - .httpBasic(Customizer.withDefaults()) - .oauth2ResourceServer((oauth2) -> oauth2 - .jwt((jwt) -> jwt.jwkSetUri(registration.getProviderDetails().getJwkSetUri())) - ); - // @formatter:off - - return http.build(); - } - - @Bean - ReactiveUserDetailsService users(ClientRegistration registration) { - return new MapReactiveUserDetailsService(User.withUsername(registration.getClientId()) - .password("{noop}" + registration.getClientSecret()).authorities("APP").build()); - } - - @GetMapping("/login/oauth/authorize") - String nonce(@RequestParam("nonce") String nonce, @RequestParam("state") String state) { - this.nonce = nonce; - return state; - } - - @PostMapping("/token") - Map accessToken(WebSession session) { - JwtEncoderParameters parameters = JwtEncoderParameters - .from(JwtClaimsSet.builder().id("id").subject(this.username) - .issuer(getIssuerUri()).issuedAt(Instant.now()) - .expiresAt(Instant.now().plusSeconds(86400)).claim("scope", "openid").build()); - String token = this.encoder.encode(parameters).getTokenValue(); - return new OIDCTokens(idToken(session.getId()), new BearerAccessToken(token, 86400, new Scope("openid")), null) - .toJSONObject(); - } - - String idToken(String sessionId) { - OidcIdToken token = TestOidcIdTokens.idToken().issuer(getIssuerUri()) - .subject(this.username).expiresAt(Instant.now().plusSeconds(86400)) - .audience(List.of(this.registration.getClientId())).nonce(this.nonce) - .claim(LogoutTokenClaimNames.SID, sessionId).build(); - JwtEncoderParameters parameters = JwtEncoderParameters - .from(JwtClaimsSet.builder().claims((claims) -> claims.putAll(token.getClaims())).build()); - return this.encoder.encode(parameters).getTokenValue(); - } - - private String getIssuerUri() { - if (this.web == null) { - return TestClientRegistrations.clientRegistration().build().getProviderDetails().getIssuerUri(); - } - return this.web.url("/").toString(); - } - - @GetMapping("/user") - Map userinfo() { - return Map.of("sub", this.username, "id", this.username); - } - - @GetMapping("/jwks") - String jwks() { - return new JWKSet(key).toString(); - } - - @GetMapping("/token/logout") - String logoutToken(@AuthenticationPrincipal OidcUser user) { - OidcLogoutToken token = TestOidcLogoutTokens.withUser(user) - .audience(List.of(this.registration.getClientId())).build(); - JwsHeader header = JwsHeader.with(SignatureAlgorithm.RS256).type("logout+jwt").build(); - JwtClaimsSet claims = JwtClaimsSet.builder().claims((c) -> c.putAll(token.getClaims())).build(); - JwtEncoderParameters parameters = JwtEncoderParameters.from(header, claims); - return this.encoder.encode(parameters).getTokenValue(); - } - - @GetMapping("/token/logout/all") - String logoutTokenAll(@AuthenticationPrincipal OidcUser user) { - OidcLogoutToken token = TestOidcLogoutTokens.withUser(user) - .audience(List.of(this.registration.getClientId())) - .claims((claims) -> claims.remove(LogoutTokenClaimNames.SID)).build(); - JwsHeader header = JwsHeader.with(SignatureAlgorithm.RS256).type("JWT").build(); - JwtClaimsSet claims = JwtClaimsSet.builder().claims((c) -> c.putAll(token.getClaims())).build(); - JwtEncoderParameters parameters = JwtEncoderParameters.from(header, claims); - return this.encoder.encode(parameters).getTokenValue(); - } - } - - @Configuration - static class WebServerConfig { - - private final MockWebServer server = new MockWebServer(); - - @Bean - MockWebServer web(ObjectProvider web) { - this.server.setDispatcher(new WebTestClientDispatcher(web)); - return this.server; - } - - @PreDestroy - void shutdown() throws IOException { - this.server.shutdown(); - } - - } - - private static class WebTestClientDispatcher extends Dispatcher { - - private final ObjectProvider webProvider; - - private WebTestClient web; - - private Consumer assertion = (rr) -> { }; - - WebTestClientDispatcher(ObjectProvider web) { - this.webProvider = web; - } - - @Override - public MockResponse dispatch(RecordedRequest request) throws InterruptedException { - this.assertion.accept(request); - this.web = this.webProvider.getObject(); - String method = request.getMethod(); - String path = request.getPath(); - String csrf = request.getHeader("X-CSRF-TOKEN"); - String sessionId = session(request); - WebTestClient.RequestHeadersSpec r; - if ("GET".equals(method)) { - r = this.web.get().uri(path); - } - else { - WebTestClient.RequestBodySpec body; - if (csrf == null) { - body = this.web.mutateWith(csrf()).post().uri(path); - } - else { - body = this.web.post().uri(path).header("X-CSRF-TOKEN", csrf); - } - body.body(BodyInserters.fromValue(request.getBody().readUtf8())); - r = body; - } - for (Map.Entry> header : request.getHeaders().toMultimap().entrySet()) { - if (header.getKey().equalsIgnoreCase("Cookie")) { - continue; - } - r.header(header.getKey(), header.getValue().iterator().next()); - } - if (sessionId != null) { - r.cookie(SESSION_COOKIE_NAME, sessionId); - } - - try { - FluxExchangeResult result = r.exchange().returnResult(String.class); - return toMockResponse(result); - } - catch (Exception ex) { - MockResponse response = new MockResponse(); - response.setResponseCode(500); - response.setBody(ex.getMessage()); - return response; - } - } - - void setAssertion(Consumer assertion) { - this.assertion = assertion; - } - - private String session(RecordedRequest request) { - String cookieHeaderValue = request.getHeader("Cookie"); - if (cookieHeaderValue == null) { - return null; - } - String[] cookies = cookieHeaderValue.split(";"); - for (String cookie : cookies) { - String[] parts = cookie.split("="); - if (SESSION_COOKIE_NAME.equals(parts[0])) { - return parts[1]; - } - if ("JSESSIONID".equals(parts[0])) { - return parts[1]; - } - } - return null; - } - - private MockResponse toMockResponse(FluxExchangeResult result) { - MockResponse response = new MockResponse(); - response.setResponseCode(result.getStatus().value()); - for (String name : result.getResponseHeaders().headerNames()) { - response.addHeader(name, result.getResponseHeaders().getFirst(name)); - } - String body = result.getResponseBody().blockFirst(); - if (body != null) { - response.setBody(body); - } - return response; - } - - } - -} diff --git a/config/src/test/java/org/springframework/security/config/web/server/OneTimeTokenLoginSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/OneTimeTokenLoginSpecTests.java deleted file mode 100644 index b4766c676e4..00000000000 --- a/config/src/test/java/org/springframework/security/config/web/server/OneTimeTokenLoginSpecTests.java +++ /dev/null @@ -1,548 +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.web.server; - -import java.util.Collections; -import java.util.Map; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentMatchers; -import org.mockito.Mockito; -import reactor.core.publisher.Mono; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; -import org.springframework.http.MediaType; -import org.springframework.security.authentication.ott.OneTimeToken; -import org.springframework.security.config.Customizer; -import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; -import org.springframework.security.config.test.SpringTestContext; -import org.springframework.security.config.test.SpringTestContextExtension; -import org.springframework.security.core.userdetails.MapReactiveUserDetailsService; -import org.springframework.security.core.userdetails.ReactiveUserDetailsService; -import org.springframework.security.core.userdetails.User; -import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers; -import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler; -import org.springframework.security.web.server.authentication.ott.DefaultServerGenerateOneTimeTokenRequestResolver; -import org.springframework.security.web.server.authentication.ott.ServerGenerateOneTimeTokenRequestResolver; -import org.springframework.security.web.server.authentication.ott.ServerOneTimeTokenGenerationSuccessHandler; -import org.springframework.security.web.server.authentication.ott.ServerRedirectOneTimeTokenGenerationSuccessHandler; -import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.web.reactive.config.EnableWebFlux; -import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.server.ServerWebExchange; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatException; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -/** - * Tests for {@link ServerHttpSecurity.OneTimeTokenLoginSpec} - * - * @author Max Batischev - */ -@ExtendWith(SpringTestContextExtension.class) -public class OneTimeTokenLoginSpecTests { - - public final SpringTestContext spring = new SpringTestContext(this); - - private WebTestClient client; - - private static final String EXPECTED_HTML_HEAD = """ - - - - - - - - Please sign in - - - """; - - private static final String LOGIN_PART = """ -