1
0
mirror of synced 2026-08-07 10:47:49 +00:00

Compare commits

...

192 Commits

Author SHA1 Message Date
dependabot[bot] e496dea34a Bump org.seleniumhq.selenium:selenium-java from 4.43.0 to 4.46.0
Bumps [org.seleniumhq.selenium:selenium-java](https://github.com/SeleniumHQ/selenium) from 4.43.0 to 4.46.0.
- [Release notes](https://github.com/SeleniumHQ/selenium/releases)
- [Commits](https://github.com/SeleniumHQ/selenium/compare/selenium-4.43.0...selenium-4.46.0)

---
updated-dependencies:
- dependency-name: org.seleniumhq.selenium:selenium-java
  dependency-version: 4.46.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-13 03:05:44 +00:00
dependabot[bot] 6680d42d34 Bump ch.qos.logback:logback-classic from 1.5.37 to 1.5.38
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.37 to 1.5.38.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.37...v_1.5.38)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-version: 1.5.38
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-10 03:17:42 +00:00
dependabot[bot] c2efa64c4d Bump actions/setup-java from 5.4.0 to 5.5.0
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.4.0 to 5.5.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/1bcf9fb12cf4aa7d266a90ae39939e61372fe520...0f481fcb613427c0f801b606911222b5b6f3083a)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 5.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-10 00:57:47 +00:00
Josh Cummings 0a75d5b785 Merge branch '7.0.x' 2026-07-09 11:02:02 -06:00
Josh Cummings d2366650ec Polish Serialization Test
This commit makes the version calculation work for
intermediate version numbers like 7.0.5.1
2026-07-09 11:01:06 -06:00
Josh Cummings 39790c0a7e Merge branch '7.0.x' 2026-07-09 08:44:09 -06:00
Josh Cummings 5338b7a05b Move Wildcards to Minor Versions
This commit targets the major.minor version pair that
matches Spring Security 7.0.x

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-07-09 08:43:57 -06:00
Josh Cummings 71f81dede2 Merge branch '7.0.x' 2026-07-09 08:32:45 -06:00
Josh Cummings bd86d0b233 Add Micrometer to Snapshot Test
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-07-09 08:32:11 -06:00
Josh Cummings f8442794a8 Check Only JDK 17
This commit removes the extra build for
JDK 21 to save time on builds.

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-07-09 08:32:11 -06:00
Josh Cummings b62affe126 Correct Copyright Headers
This commit updates all headers to have
the same date range.

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-07-08 17:11:04 -06:00
dependabot[bot] 60e7733d8a Bump com.fasterxml.jackson:jackson-bom from 2.22.0 to 2.22.1
Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.22.0 to 2.22.1.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.22.0...jackson-bom-2.22.1)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson:jackson-bom
  dependency-version: 2.22.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-08 03:15:25 +00:00
dependabot[bot] 2bac0be1d6 Bump org.hibernate.orm:hibernate-core from 7.4.3.Final to 7.4.4.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 7.4.3.Final to 7.4.4.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.4.4/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/7.4.3...7.4.4)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 7.4.4.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-06 03:20:54 +00:00
dependabot[bot] bb51ad6096 Bump com.webauthn4j:webauthn4j-core
Bumps [com.webauthn4j:webauthn4j-core](https://github.com/webauthn4j/webauthn4j) from 0.31.7.RELEASE to 0.31.8.RELEASE.
- [Release notes](https://github.com/webauthn4j/webauthn4j/releases)
- [Commits](https://github.com/webauthn4j/webauthn4j/compare/0.31.7.RELEASE...0.31.8.RELEASE)

---
updated-dependencies:
- dependency-name: com.webauthn4j:webauthn4j-core
  dependency-version: 0.31.8.RELEASE
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 03:12:59 +00:00
dependabot[bot] 9161eced5b Bump org.hibernate.orm:hibernate-core from 7.4.2.Final to 7.4.3.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 7.4.2.Final to 7.4.3.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.4.3/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/7.4.2...7.4.3)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 7.4.3.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-30 03:16:50 +00:00
dependabot[bot] 769fd66ed3 Bump org.apache.httpcomponents.client5:httpclient5 from 5.6.1 to 5.6.2
Bumps [org.apache.httpcomponents.client5:httpclient5](https://github.com/apache/httpcomponents-client) from 5.6.1 to 5.6.2.
- [Changelog](https://github.com/apache/httpcomponents-client/blob/rel/v5.6.2/RELEASE_NOTES.txt)
- [Commits](https://github.com/apache/httpcomponents-client/compare/rel/v5.6.1...rel/v5.6.2)

---
updated-dependencies:
- dependency-name: org.apache.httpcomponents.client5:httpclient5
  dependency-version: 5.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-30 03:16:17 +00:00
dependabot[bot] 49ecfa71dd Bump gradle-wrapper from 9.6.0 to 9.6.1
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.6.0 to 9.6.1.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.6.0...v9.6.1)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-29 03:18:05 +00:00
dependabot[bot] c6d41fd1a5 Bump org.junit:junit-bom from 6.1.0 to 6.1.1
Bumps [org.junit:junit-bom](https://github.com/junit-team/junit-framework) from 6.1.0 to 6.1.1.
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r6.1.0...r6.1.1)

---
updated-dependencies:
- dependency-name: org.junit:junit-bom
  dependency-version: 6.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-29 03:16:18 +00:00
dependabot[bot] 316cf90ec2 Bump ch.qos.logback:logback-classic from 1.5.36 to 1.5.37
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.36 to 1.5.37.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.36...v_1.5.37)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-version: 1.5.37
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-29 03:15:30 +00:00
dependabot[bot] f5fbc2b46b Bump ch.qos.logback:logback-classic from 1.5.35 to 1.5.36
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.35 to 1.5.36.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.35...v_1.5.36)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-version: 1.5.36
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-26 03:16:22 +00:00
dependabot[bot] 49d2ebf3cf Bump org.apache.kerby:kerb-simplekdc from 2.1.1 to 2.1.2
Bumps org.apache.kerby:kerb-simplekdc from 2.1.1 to 2.1.2.

---
updated-dependencies:
- dependency-name: org.apache.kerby:kerb-simplekdc
  dependency-version: 2.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-26 03:14:57 +00:00
dependabot[bot] fc2c927a72 Bump antora from 3.2.0-alpha.12 to 3.2.0-rc.2 in /docs
---
updated-dependencies:
- dependency-name: antora
  dependency-version: 3.2.0-rc.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-26 01:04:48 +00:00
dependabot[bot] 40de3ed6bb Bump actions/setup-java from 5.3.0 to 5.4.0
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.3.0 to 5.4.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/ad2b38190b15e4d6bdf0c97fb4fca8412226d287...1bcf9fb12cf4aa7d266a90ae39939e61372fe520)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 5.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-26 01:02:28 +00:00
dependabot[bot] 85e8221ba0 Bump io.spring.nullability:io.spring.nullability.gradle.plugin
Bumps [io.spring.nullability:io.spring.nullability.gradle.plugin](https://github.com/spring-gradle-plugins/nullability-plugin) from 0.0.13 to 0.0.14.
- [Release notes](https://github.com/spring-gradle-plugins/nullability-plugin/releases)
- [Commits](https://github.com/spring-gradle-plugins/nullability-plugin/compare/v0.0.13...v0.0.14)

---
updated-dependencies:
- dependency-name: io.spring.nullability:io.spring.nullability.gradle.plugin
  dependency-version: 0.0.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-25 03:16:31 +00:00
dependabot[bot] 7f753a2433 Bump ch.qos.logback:logback-classic from 1.5.34 to 1.5.35
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.34 to 1.5.35.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.34...v_1.5.35)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-version: 1.5.35
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-24 03:14:02 +00:00
dependabot[bot] ed7ae7969e Bump org.hibernate.orm:hibernate-core from 7.4.1.Final to 7.4.2.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 7.4.1.Final to 7.4.2.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.4.2/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/7.4.1...7.4.2)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 7.4.2.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-22 03:16:09 +00:00
dependabot[bot] 4ddef4ed83 Bump gradle-wrapper from 9.5.1 to 9.6.0
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.5.1 to 9.6.0.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.5.1...v9.6.0)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-19 03:20:36 +00:00
dependabot[bot] abb8aa54d8 Bump org-opensaml5 from 5.2.2 to 5.2.3
Bumps `org-opensaml5` from 5.2.2 to 5.2.3.

Updates `org.opensaml:opensaml-saml-api` from 5.2.2 to 5.2.3

Updates `org.opensaml:opensaml-saml-impl` from 5.2.2 to 5.2.3

---
updated-dependencies:
- dependency-name: org.opensaml:opensaml-saml-api
  dependency-version: 5.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.opensaml:opensaml-saml-impl
  dependency-version: 5.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-19 03:17:49 +00:00
dependabot[bot] deb298ee81 Bump actions/checkout from 6.0.3 to 7.0.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.3 to 7.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/df4cb1c069e1874edd31b4311f1884172cec0e10...9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-19 01:03:52 +00:00
dependabot[bot] 3565d9bd9d Bump js-yaml from 4.1.1 to 4.2.0 in /javascript
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.1 to 4.2.0.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/commits)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.2.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-19 01:02:56 +00:00
dependabot[bot] 827a689ef8 Bump actions/setup-java from 5.2.0 to 5.3.0
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/be666c2fcd27ec809703dec50e508c2fdc7f6654...ad2b38190b15e4d6bdf0c97fb4fca8412226d287)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-version: 5.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-19 00:46:33 +00:00
dependabot[bot] 43f958e058 Bump esbuild from 0.25.0 to 0.28.1 in /javascript
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.25.0 to 0.28.1.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2025.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.0...v0.28.1)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.28.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-14 01:16:55 +00:00
dependabot[bot] 5594fc018f Bump com.unboundid:unboundid-ldapsdk from 7.0.4 to 7.0.5
Bumps [com.unboundid:unboundid-ldapsdk](https://github.com/pingidentity/ldapsdk) from 7.0.4 to 7.0.5.
- [Release notes](https://github.com/pingidentity/ldapsdk/releases)
- [Changelog](https://github.com/pingidentity/ldapsdk/blob/master/docs/release-notes.html)
- [Commits](https://github.com/pingidentity/ldapsdk/commits)

---
updated-dependencies:
- dependency-name: com.unboundid:unboundid-ldapsdk
  dependency-version: 7.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-12 03:17:11 +00:00
dependabot[bot] bf06d19225 Bump com.webauthn4j:webauthn4j-core
Bumps [com.webauthn4j:webauthn4j-core](https://github.com/webauthn4j/webauthn4j) from 0.31.6.RELEASE to 0.31.7.RELEASE.
- [Release notes](https://github.com/webauthn4j/webauthn4j/releases)
- [Commits](https://github.com/webauthn4j/webauthn4j/compare/0.31.6.RELEASE...0.31.7.RELEASE)

---
updated-dependencies:
- dependency-name: com.webauthn4j:webauthn4j-core
  dependency-version: 0.31.7.RELEASE
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-10 03:17:13 +00:00
dependabot[bot] d511a4badb Bump org.hibernate.orm:hibernate-core from 7.4.0.Final to 7.4.1.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 7.4.0.Final to 7.4.1.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.4.1/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/7.4.0...7.4.1)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 7.4.1.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-10 03:15:52 +00:00
Joe Grandja 038a6601d8 Merge branch '7.0.x' 2026-06-09 14:30:59 -04:00
Joe Grandja 57c2af4900 Merge branch '6.5.x' into 7.0.x 2026-06-09 14:30:37 -04:00
github-actions[bot] 4690f1bb8a Next development version 2026-06-09 16:39:34 +00:00
github-actions[bot] 6da4ea0d0d Next development version 2026-06-09 16:06:39 +00:00
github-actions[bot] 8078781a6b Release 7.1.0 2026-06-09 16:05:12 +00:00
github-actions[bot] 8f69721f24 Next development version 2026-06-09 16:02:59 +00:00
github-actions[bot] acd131c3d1 Release 7.0.6 2026-06-09 15:31:10 +00:00
github-actions[bot] 73b077790f Release 6.5.11 2026-06-09 15:25:02 +00:00
Joe Grandja 835ca03dd9 Merge branch '7.0.x' 2026-06-09 10:10:19 -04:00
Joe Grandja 15e2488c84 Sync branch 'main' 2026-06-09 10:09:16 -04:00
Joe Grandja 62c4d60524 Merge branch '6.5.x' into 7.0.x 2026-06-09 10:01:20 -04:00
Joe Grandja d884913175 Sync branch '7.0.x' 2026-06-09 10:00:13 -04:00
Joe Grandja fd5dae5e4d Sync branch '6.5.x' 2026-06-09 09:57:02 -04:00
Joe Grandja 65f43d6894 Merge branch '7.0.x' 2026-06-09 09:16:18 -04:00
Joe Grandja d29c761135 Merge branch '6.5.x' into 7.0.x 2026-06-09 09:04:33 -04:00
dependabot[bot] 700a4533aa Bump ch.qos.logback:logback-classic from 1.5.32 to 1.5.34
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.32 to 1.5.34.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.32...v_1.5.34)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-version: 1.5.34
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 08:27:37 -04:00
Joe Grandja 831bfd7ffa Update to spring-data-bom 2026.0.0
Closes gh-19303
2026-06-09 08:24:55 -04:00
Joe Grandja 3a394c633f Update micrometer-bom to 1.15.12
Closes gh-19302
2026-06-09 08:18:31 -04:00
Joe Grandja 79f9bec818 Update to reactor-bom 2024.0.18
Closes gh-19301
2026-06-09 08:18:30 -04:00
dependabot[bot] 5b019365b7 Bump org.hibernate.orm:hibernate-core from 6.6.51.Final to 6.6.53.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.51.Final to 6.6.53.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.53/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.51...6.6.53)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 6.6.53.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 07:37:50 -04:00
dependabot[bot] 038da0289e Bump io.projectreactor:reactor-bom from 2025.0.5 to 2025.0.6
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2025.0.5 to 2025.0.6.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2025.0.5...2025.0.6)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-bom
  dependency-version: 2025.0.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 07:37:14 -04:00
dependabot[bot] f9b4afdfc5 Bump com.fasterxml.jackson:jackson-bom from 2.18.7 to 2.18.8
Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.18.7 to 2.18.8.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.18.7...jackson-bom-2.18.8)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson:jackson-bom
  dependency-version: 2.18.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 07:36:02 -04:00
dependabot[bot] 65aaedbc83 Bump org.springframework.data:spring-data-bom from 2025.1.5 to 2025.1.6
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2025.1.5 to 2025.1.6.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2025.1.5...2025.1.6)

---
updated-dependencies:
- dependency-name: org.springframework.data:spring-data-bom
  dependency-version: 2025.1.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 07:33:17 -04:00
dependabot[bot] f325ddbdaa Bump org.springframework:spring-framework-bom from 6.2.18 to 6.2.19
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 6.2.18 to 6.2.19.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v6.2.18...v6.2.19)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-version: 6.2.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 07:32:45 -04:00
dependabot[bot] bc9bdbaf83 Bump io-micrometer from 1.16.5 to 1.16.6
Bumps `io-micrometer` from 1.16.5 to 1.16.6.

Updates `io.micrometer:micrometer-bom` from 1.16.5 to 1.16.6
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.16.5...v1.16.6)

Updates `io.micrometer:micrometer-observation` from 1.16.5 to 1.16.6
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.16.5...v1.16.6)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-bom
  dependency-version: 1.16.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.micrometer:micrometer-observation
  dependency-version: 1.16.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 07:32:09 -04:00
dependabot[bot] 0148e9fdb3 Bump org.springframework.data:spring-data-bom from 2025.1.5 to 2025.1.6
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2025.1.5 to 2025.1.6.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2025.1.5...2025.1.6)

---
updated-dependencies:
- dependency-name: org.springframework.data:spring-data-bom
  dependency-version: 2025.1.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 11:05:58 +00:00
dependabot[bot] dcc03f1a20 Bump io.projectreactor:reactor-bom from 2025.0.5 to 2025.0.6
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2025.0.5 to 2025.0.6.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2025.0.5...2025.0.6)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-bom
  dependency-version: 2025.0.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 10:54:01 +00:00
dependabot[bot] 5cc80523c0 Bump org.springframework.ldap:spring-ldap-core from 4.0.3 to 4.1.0
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 4.0.3 to 4.1.0.
- [Release notes](https://github.com/spring-projects/spring-ldap/releases)
- [Changelog](https://github.com/spring-projects/spring-ldap/blob/main/changelog.txt)
- [Commits](https://github.com/spring-projects/spring-ldap/compare/4.0.3...4.1.0)

---
updated-dependencies:
- dependency-name: org.springframework.ldap:spring-ldap-core
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 10:51:27 +00:00
Joe Grandja 0d43ee7a15 Merge branch '7.0.x' 2026-06-09 06:26:10 -04:00
Joe Grandja 6da29433cc Merge branch '6.5.x' into 7.0.x 2026-06-09 06:23:22 -04:00
dependabot[bot] 4adfdf66b9 Bump org.apache.maven:maven-resolver-provider from 3.9.15 to 3.9.16
Bumps org.apache.maven:maven-resolver-provider from 3.9.15 to 3.9.16.

---
updated-dependencies:
- dependency-name: org.apache.maven:maven-resolver-provider
  dependency-version: 3.9.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 05:50:28 -04:00
dependabot[bot] e91b81a5aa Bump org-bouncycastle from 1.80 to 1.80.2
Bumps `org-bouncycastle` from 1.80 to 1.80.2.

Updates `org.bouncycastle:bcpkix-jdk18on` from 1.80 to 1.80.2
- [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

Updates `org.bouncycastle:bcprov-jdk18on` from 1.80 to 1.80.2
- [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

---
updated-dependencies:
- dependency-name: org.bouncycastle:bcpkix-jdk18on
  dependency-version: 1.80.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.bouncycastle:bcprov-jdk18on
  dependency-version: 1.80.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 05:49:36 -04:00
dependabot[bot] 0949d3b3ea Bump org.slf4j:slf4j-api from 2.0.17 to 2.0.18
Bumps org.slf4j:slf4j-api from 2.0.17 to 2.0.18.

---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-api
  dependency-version: 2.0.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 05:33:00 -04:00
dependabot[bot] d44efc899d Bump org.hibernate.orm:hibernate-core from 6.6.50.Final to 6.6.51.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.50.Final to 6.6.51.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.51/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.50...6.6.51)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 6.6.51.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 05:29:40 -04:00
dependabot[bot] c5870a746a Bump @springio/antora-extensions from 1.14.11 to 1.14.12 in /docs
Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.14.11 to 1.14.12.
- [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/antora-extensions/compare/v1.14.11...v1.14.12)

---
updated-dependencies:
- dependency-name: "@springio/antora-extensions"
  dependency-version: 1.14.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 05:28:18 -04:00
dependabot[bot] 58bca0e895 Bump @springio/antora-extensions from 1.14.11 to 1.14.12 in /docs
Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.14.11 to 1.14.12.
- [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/antora-extensions/compare/v1.14.11...v1.14.12)

---
updated-dependencies:
- dependency-name: "@springio/antora-extensions"
  dependency-version: 1.14.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 05:27:42 -04:00
dependabot[bot] 1bc3fd9ea9 Bump io.mockk:mockk from 1.14.9 to 1.14.11
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.14.9 to 1.14.11.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.9...v1.14.11)

---
updated-dependencies:
- dependency-name: io.mockk:mockk
  dependency-version: 1.14.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 05:26:59 -04:00
dependabot[bot] e84b949719 Bump com.webauthn4j:webauthn4j-core
Bumps [com.webauthn4j:webauthn4j-core](https://github.com/webauthn4j/webauthn4j) from 0.31.5.RELEASE to 0.31.6.RELEASE.
- [Release notes](https://github.com/webauthn4j/webauthn4j/releases)
- [Commits](https://github.com/webauthn4j/webauthn4j/compare/0.31.5.RELEASE...0.31.6.RELEASE)

---
updated-dependencies:
- dependency-name: com.webauthn4j:webauthn4j-core
  dependency-version: 0.31.6.RELEASE
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 05:24:07 -04:00
dependabot[bot] 33bd1abb9d Bump ch.qos.logback:logback-classic from 1.5.32 to 1.5.34
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.32 to 1.5.34.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.32...v_1.5.34)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-version: 1.5.34
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 05:23:13 -04:00
dependabot[bot] ea62b200f7 Bump actions/checkout from 6.0.2 to 6.0.3
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.2 to 6.0.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...df4cb1c069e1874edd31b4311f1884172cec0e10)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 05:22:11 -04:00
dependabot[bot] 5eb41b8a06 Bump actions/checkout from 6.0.2 to 6.0.3
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.2 to 6.0.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...df4cb1c069e1874edd31b4311f1884172cec0e10)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 05:21:32 -04:00
dependabot[bot] 8f0c077fc7 Bump org.springframework:spring-framework-bom from 7.0.7 to 7.0.8
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 7.0.7 to 7.0.8.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v7.0.7...v7.0.8)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-version: 7.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 05:19:47 -04:00
dependabot[bot] 430b9bb8c6 Bump org.springframework.ldap:spring-ldap-core from 4.0.3 to 4.0.4
Bumps [org.springframework.ldap:spring-ldap-core](https://github.com/spring-projects/spring-ldap) from 4.0.3 to 4.0.4.
- [Release notes](https://github.com/spring-projects/spring-ldap/releases)
- [Changelog](https://github.com/spring-projects/spring-ldap/blob/main/changelog.txt)
- [Commits](https://github.com/spring-projects/spring-ldap/compare/4.0.3...4.0.4)

---
updated-dependencies:
- dependency-name: org.springframework.ldap:spring-ldap-core
  dependency-version: 4.0.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 05:19:11 -04:00
dependabot[bot] 80f6f19a7e Bump org.springframework:spring-framework-bom from 7.0.7 to 7.0.8
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 7.0.7 to 7.0.8.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v7.0.7...v7.0.8)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-version: 7.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 03:35:21 +00:00
dependabot[bot] c13643d84d Bump io-micrometer from 1.16.5 to 1.17.0
Bumps `io-micrometer` from 1.16.5 to 1.17.0.

Updates `io.micrometer:micrometer-bom` from 1.16.5 to 1.17.0
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.16.5...v1.17.0)

Updates `io.micrometer:micrometer-observation` from 1.16.5 to 1.17.0
- [Release notes](https://github.com/micrometer-metrics/micrometer/releases)
- [Commits](https://github.com/micrometer-metrics/micrometer/compare/v1.16.5...v1.17.0)

---
updated-dependencies:
- dependency-name: io.micrometer:micrometer-bom
  dependency-version: 1.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: io.micrometer:micrometer-observation
  dependency-version: 1.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 03:18:44 +00:00
dependabot[bot] 0f2b4ac630 Bump tools.jackson:jackson-bom from 3.1.4 to 3.2.0
Bumps [tools.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 3.1.4 to 3.2.0.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-3.1.4...jackson-bom-3.2.0)

---
updated-dependencies:
- dependency-name: tools.jackson:jackson-bom
  dependency-version: 3.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-09 03:15:58 +00:00
dependabot[bot] d85666cbd6 Bump actions/checkout from 6.0.2 to 6.0.3
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.2 to 6.0.3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/de0fac2e4500dabe0009e67214ff5f5447ce83dd...df4cb1c069e1874edd31b4311f1884172cec0e10)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-05 00:44:29 +00:00
Joe Grandja 8c6683ab61 Merge branch '7.0.x' 2026-06-04 15:04:41 -04:00
Joe Grandja faea9352dc Merge branch 'main' 2026-06-04 15:03:28 -04:00
Joe Grandja 0fa81bfb6d Merge branch '7.0.x' 2026-06-04 14:55:31 -04:00
dependabot[bot] 7ba133db7e Bump com.fasterxml.jackson:jackson-bom from 2.21.3 to 2.22.0
Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.21.3 to 2.22.0.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.21.3...jackson-bom-2.22.0)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson:jackson-bom
  dependency-version: 2.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-04 04:54:39 +00:00
dependabot[bot] f1d35b022a Bump ch.qos.logback:logback-classic from 1.5.33 to 1.5.34
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.33 to 1.5.34.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.33...v_1.5.34)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-version: 1.5.34
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-04 04:39:17 +00:00
dependabot[bot] 80dcf01ce5 Bump org-jetbrains-kotlin from 2.3.21 to 2.4.0
Bumps `org-jetbrains-kotlin` from 2.3.21 to 2.4.0.

Updates `org.jetbrains.kotlin:kotlin-bom` from 2.3.21 to 2.4.0
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.3.21...v2.4.0)

Updates `org.jetbrains.kotlin:kotlin-gradle-plugin` from 2.3.21 to 2.4.0
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.3.21...v2.4.0)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-bom
  dependency-version: 2.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-version: 2.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-03 21:28:48 +00:00
dependabot[bot] 26416a1a8d Bump com.webauthn4j:webauthn4j-core
Bumps [com.webauthn4j:webauthn4j-core](https://github.com/webauthn4j/webauthn4j) from 0.31.5.RELEASE to 0.31.6.RELEASE.
- [Release notes](https://github.com/webauthn4j/webauthn4j/releases)
- [Commits](https://github.com/webauthn4j/webauthn4j/compare/0.31.5.RELEASE...0.31.6.RELEASE)

---
updated-dependencies:
- dependency-name: com.webauthn4j:webauthn4j-core
  dependency-version: 0.31.6.RELEASE
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-03 21:24:57 +00:00
Josh Cummings f83dac8cd2 Merge branch '7.0.x' 2026-06-02 12:43:16 -06:00
Josh Cummings 54e9575391 Merge branch '6.5.x' into 7.0.x 2026-06-02 12:42:44 -06:00
Josh Cummings e3ad551ab3 Backport SubjectX500PrincipalExtractor
This commit backports SubjectX500PrincipalExtractor so as to provide
folks moving from 6.x to 7.x a migration path from
SubjectDnX509PrincipalExtractor.

Issue gh-16980

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-06-01 14:30:33 -06:00
Josh Cummings 1e30882290 Merge branch '7.0.x' 2026-06-01 14:07:53 -06:00
Josh Cummings 7fc97c6ee2 Select Most Specific RDN in X500 Principal Extractor
SubjectX500PrincipalExtractor now returns the left-most, most-specific
matching RDN value, matching both convention and
SubjectDnX509PrincipalExtractor. Since LdapName#getRdns lists RDNs
most-significant first, the extractor reads them in reverse so that a
subject with more than one matching attribute (e.g. two CNs) resolves to
the most specific value.

Closes gh-19254

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-06-01 14:05:58 -06:00
dependabot[bot] 4a5379ee7e Bump tools.jackson:jackson-bom from 3.1.3 to 3.1.4
Bumps [tools.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 3.1.3 to 3.1.4.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-3.1.3...jackson-bom-3.1.4)

---
updated-dependencies:
- dependency-name: tools.jackson:jackson-bom
  dependency-version: 3.1.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-01 05:26:30 +00:00
dependabot[bot] 180bec35dc Bump io.mockk:mockk from 1.14.9 to 1.14.11
Bumps [io.mockk:mockk](https://github.com/mockk/mockk) from 1.14.9 to 1.14.11.
- [Release notes](https://github.com/mockk/mockk/releases)
- [Commits](https://github.com/mockk/mockk/compare/1.14.9...v1.14.11)

---
updated-dependencies:
- dependency-name: io.mockk:mockk
  dependency-version: 1.14.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-01 05:25:18 +00:00
Josh Cummings 2cff85e380 Merge branch 7.0.x 2026-05-29 15:25:40 -06:00
Josh Cummings 241bf2c148 Merge branch 6.5.x into 7.0.x 2026-05-29 15:06:52 -06:00
Josh Cummings cf06871200 Rework Logout Validation Logic
This commit simplifies the code, favoring the construction
of a list of error codes over composing a set of consumers.
This will simplify future analysis by making the code more
readable.

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-29 14:45:44 -06:00
Josh Cummings e50c2a6a74 Rework Login Validation Logic
This commit simplifies the validation code, favoring the
construction of a list of error codes over using an
accumulating Saml2ResponseValidationResult. This will
simplify future analysis by making the code more readable.

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-29 14:45:36 -06:00
Josh Cummings 30c2c8dc68 Merge branch 7.0.x 2026-05-29 14:43:34 -06:00
Josh Cummings b175e4487e Merge branch 6.5.x into 7.0.x 2026-05-29 14:31:43 -06:00
Josh Cummings 356131b1ea Use FormPostRedirectStrategy
Closes gh-16673

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-29 11:56:50 -06:00
Josh Cummings 6e17259030 Merge branch 7.0.x 2026-05-29 11:50:00 -06:00
Josh Cummings 97a49aa3bf Add setRowMapper
This commit makes so that the strategy for reading an
AssertingPartyMetadata can be configured.

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-29 11:48:20 -06:00
dependabot[bot] 83f5dd0603 Bump @springio/antora-extensions from 1.14.11 to 1.14.12 in /docs
Bumps [@springio/antora-extensions](https://github.com/spring-io/antora-extensions) from 1.14.11 to 1.14.12.
- [Changelog](https://github.com/spring-io/antora-extensions/blob/main/CHANGELOG.adoc)
- [Commits](https://github.com/spring-io/antora-extensions/compare/v1.14.11...v1.14.12)

---
updated-dependencies:
- dependency-name: "@springio/antora-extensions"
  dependency-version: 1.14.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-29 00:54:55 +00:00
Joe Grandja dce01c5701 Merge branch '7.0.x' 2026-05-28 11:09:59 -04:00
Joe Grandja e4a78c26d6 Polish redirect flows 2026-05-28 10:25:03 -04:00
Joe Grandja 9027e10b00 Merge branch '7.0.x' 2026-05-28 06:56:18 -04:00
Joe Grandja c50e4c02bb Merge branch '6.5.x' into 7.0.x 2026-05-28 06:55:13 -04:00
Josh Cummings a14c9d66b1 Favor Relative URIs in CookieRequestCache
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-28 06:54:59 -04:00
Joe Grandja f1d6451ded Merge branch '7.0.x' 2026-05-28 06:46:22 -04:00
Joe Grandja 2924dd29f7 Merge branch '6.5.x' into 7.0.x 2026-05-28 06:39:55 -04:00
Josh Cummings 9d4d9065b4 Cap Deflate Size
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-28 06:39:29 -04:00
dependabot[bot] 7c9cecc9e4 Bump ch.qos.logback:logback-classic from 1.5.32 to 1.5.33
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.5.32 to 1.5.33.
- [Release notes](https://github.com/qos-ch/logback/releases)
- [Commits](https://github.com/qos-ch/logback/compare/v_1.5.32...v_1.5.33)

---
updated-dependencies:
- dependency-name: ch.qos.logback:logback-classic
  dependency-version: 1.5.33
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 03:17:13 +00:00
Josh Cummings 5c932b222e Merge branch '7.0.x' 2026-05-27 16:44:01 -04:00
Josh Cummings b844b12dc4 Revert "Add Test Against Snapshot"
This reverts commit 6241327862.
2026-05-27 16:43:10 -04:00
Josh Cummings 3f1a794cd9 Revert "Add Test Against Snapshots"
This reverts commit c5de4096b4.
2026-05-27 16:42:53 -04:00
Josh Cummings 6241327862 Add Test Against Snapshots
This commit uses a GH workflow to use
JDK 25 to test against snapshot versions
of various dependencies

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-27 16:18:01 -04:00
Josh Cummings 6833fc4152 Merge branch '7.0.x' 2026-05-27 16:12:42 -04:00
Josh Cummings c5de4096b4 Add Test Against Snapshots
This commit uses a GH workflow that uses JDK 17 and tests
against snapshot versions of various upstream OSS projects.

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-27 16:12:29 -04:00
Joe Grandja 2ddb365607 Merge branch '7.0.x' 2026-05-27 11:29:24 -04:00
Joe Grandja 95d375c8d8 Partial revert fe2e52f646
Remove test against snapshots

Related gh-17558

Signed-off-by: Joe Grandja <10884212+jgrandja@users.noreply.github.com>
2026-05-27 11:25:11 -04:00
Joe Grandja 98f5b363ed Add micrometerVersion to check-snapshots.yml
Signed-off-by: Joe Grandja <10884212+jgrandja@users.noreply.github.com>
2026-05-27 10:06:43 -04:00
dependabot[bot] 4300a7fccb Bump org.hibernate.orm:hibernate-core from 7.3.6.Final to 7.4.0.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 7.3.6.Final to 7.4.0.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.4.0/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/7.3.6...7.4.0)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 7.4.0.Final
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-27 05:06:16 +00:00
Josh Cummings 0a321da396 Merge branch '7.0.x' 2026-05-26 16:16:36 -06:00
Josh Cummings b486d2695a Update to Micrometer 1.16.5
Closes gh-19225

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-26 16:16:16 -06:00
Josh Cummings c90acad0c2 Merge branch '7.0.x' 2026-05-26 15:31:13 -06:00
Josh Cummings 8b34113c2a Merge branch '6.5.x' into 7.0.x 2026-05-26 15:27:35 -06:00
Josh Cummings fe2e52f646 Add Micrometer Version Override
Closes gh-19187

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-26 15:18:01 -06:00
Josh Cummings 0ea87ca3bf Upgrade Micrometer to 1.15.11
Closes gh-19224

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-26 15:17:57 -06:00
dependabot[bot] 6555199ff7 Bump org.hibernate.orm:hibernate-core from 7.3.5.Final to 7.3.6.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 7.3.5.Final to 7.3.6.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.3.6/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/7.3.5...7.3.6)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 7.3.6.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 03:38:40 +00:00
Josh Cummings 02725ed7fd Merge branch '7.0.x' 2026-05-22 22:28:06 -06:00
Josh Cummings 71fd23429c Merge branch '6.5.x' into 7.0.x 2026-05-22 22:27:27 -06:00
Josh Cummings 85514a9b34 Update NodeJS to 24.15
This commit declares a NodeJS value whereas previously,
Antora would use the latest LTS version. When it updated to
Node 24.16, site production stopped, possibly due to
https://github.com/nodejs/node/commit/e95570c054.

Either way, it is helpful to have a pinned version in order
to stabilize builds, allowing us to update it in an orderly
fashion like other dependencies.

Issue gh-19206

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-22 22:27:00 -06:00
Josh Cummings 6a029fd853 Merge branch '7.0.x' 2026-05-22 22:05:47 -06:00
Josh Cummings d039e0191f Move Doc Sample to Correct Package
Issue gh-19128

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-22 22:05:29 -06:00
Josh Cummings 78685cdd77 Merge branch '7.0.x' 2026-05-22 21:37:30 -06:00
Josh Cummings cfdadfea01 Apply Builder Bean Only to Application Endpoints
A published PathPatternRequestMatcher.Builder bean is intended to align
application endpoint matchers, which commonly are from the same servlet.

This commit ensures that this bean is used by securityMatcher(s),
authorizeHttpRequests, and WebSecurity#ignoring, which target
application endpoints typically served by servlets.

This commit also adds tests ensuring that the bean is not applied to filter-chain
security endpoints such as those configured by formLogin, logout,
oneTimeTokenLogin, OAuth 2.0 Login, and SAML 2.0 Login, since
these endpoints are, by default, handled by filters independent from
any target servlet and servlet path.

Closes gh-19128

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-22 21:36:19 -06:00
Josh Cummings 4942459250 Update to nimbus-jose-jwt 10.9
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-21 19:23:26 +00:00
dependabot[bot] 22d99d20ae Bump com.nimbusds:oauth2-oidc-sdk from 11.37 to 11.37.2
Bumps [com.nimbusds:oauth2-oidc-sdk](https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions) from 11.37 to 11.37.2.
- [Changelog](https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions/src/master/CHANGELOG.txt)
- [Commits](https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions/branches/compare/11.37.2..11.37)

---
updated-dependencies:
- dependency-name: com.nimbusds:oauth2-oidc-sdk
  dependency-version: 11.37.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 19:23:26 +00:00
Josh Cummings 0c6e069454 Merge branch '7.0.x' 2026-05-21 12:55:43 -06:00
Josh Cummings f70aa75445 Merge branch '6.5.x' into 7.0.x 2026-05-21 12:55:40 -06:00
dependabot[bot] 86e0f1341e Bump com.fasterxml.jackson:jackson-bom from 2.18.6 to 2.18.7
Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.18.6 to 2.18.7.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.18.6...jackson-bom-2.18.7)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson:jackson-bom
  dependency-version: 2.18.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 12:38:00 -06:00
dependabot[bot] cea6618265 Bump org.hibernate.orm:hibernate-core from 6.6.49.Final to 6.6.50.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 6.6.49.Final to 6.6.50.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.50/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.49...6.6.50)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 6.6.50.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 12:36:57 -06:00
dependabot[bot] 5bf8f641f1 Bump spring-io/spring-gradle-build-action from 2.0.5 to 2.0.6
Bumps [spring-io/spring-gradle-build-action](https://github.com/spring-io/spring-gradle-build-action) from 2.0.5 to 2.0.6.
- [Release notes](https://github.com/spring-io/spring-gradle-build-action/releases)
- [Commits](https://github.com/spring-io/spring-gradle-build-action/compare/efc55f07f4dfa22f2afd97f9ea1be4212eeed737...c8668747d7c264864c8c7f7026d0d277d14a78dc)

---
updated-dependencies:
- dependency-name: spring-io/spring-gradle-build-action
  dependency-version: 2.0.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 12:36:52 -06:00
dependabot[bot] 8b9f556d8f Bump gradle-wrapper from 8.14.4 to 8.14.5
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 8.14.4 to 8.14.5.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v8.14.4...v8.14.5)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 8.14.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 12:36:46 -06:00
dependabot[bot] d9b8a8ec54 Bump spring-io/spring-release-actions from 0.0.4 to 0.0.5
Bumps [spring-io/spring-release-actions](https://github.com/spring-io/spring-release-actions) from 0.0.4 to 0.0.5.
- [Release notes](https://github.com/spring-io/spring-release-actions/releases)
- [Commits](https://github.com/spring-io/spring-release-actions/compare/2420148725bebe44bd59a575a9b1961ca4459b0b...a1f321783a0769dd2aea4fad6c2ae2f95a52b885)

---
updated-dependencies:
- dependency-name: spring-io/spring-release-actions
  dependency-version: 0.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 12:36:42 -06:00
dependabot[bot] 4e04c266be Bump antora from 3.2.0-alpha.11 to 3.2.0-alpha.12 in /docs
---
updated-dependencies:
- dependency-name: antora
  dependency-version: 3.2.0-alpha.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 12:36:37 -06:00
Josh Cummings a9eae04444 Merge branch '7.0.x' 2026-05-21 12:32:28 -06:00
dependabot[bot] dc5c65eaf1 Bump com.webauthn4j:webauthn4j-core
Bumps [com.webauthn4j:webauthn4j-core](https://github.com/webauthn4j/webauthn4j) from 0.31.3.RELEASE to 0.31.5.RELEASE.
- [Release notes](https://github.com/webauthn4j/webauthn4j/releases)
- [Commits](https://github.com/webauthn4j/webauthn4j/compare/0.31.3.RELEASE...0.31.5.RELEASE)

---
updated-dependencies:
- dependency-name: com.webauthn4j:webauthn4j-core
  dependency-version: 0.31.5.RELEASE
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 12:30:34 -06:00
dependabot[bot] 0353616be3 Bump org.slf4j:slf4j-api from 2.0.17 to 2.0.18
Bumps org.slf4j:slf4j-api from 2.0.17 to 2.0.18.

---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-api
  dependency-version: 2.0.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 12:29:55 -06:00
Josh Cummings 4d66130d32 Add in:build Label to Dependabot PRs
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-21 12:21:39 -06:00
dependabot[bot] 47853e1314 Bump gradle-wrapper from 8.14.4 to 8.14.5
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 8.14.4 to 8.14.5.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v8.14.4...v8.14.5)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 8.14.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 12:18:05 -06:00
dependabot[bot] 90e1658afe Bump spring-io/spring-release-actions from 0.0.4 to 0.0.5
Bumps [spring-io/spring-release-actions](https://github.com/spring-io/spring-release-actions) from 0.0.4 to 0.0.5.
- [Release notes](https://github.com/spring-io/spring-release-actions/releases)
- [Commits](https://github.com/spring-io/spring-release-actions/compare/2420148725bebe44bd59a575a9b1961ca4459b0b...a1f321783a0769dd2aea4fad6c2ae2f95a52b885)

---
updated-dependencies:
- dependency-name: spring-io/spring-release-actions
  dependency-version: 0.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 12:18:00 -06:00
dependabot[bot] e1d94d1e06 Bump antora from 3.2.0-alpha.11 to 3.2.0-alpha.12 in /docs
---
updated-dependencies:
- dependency-name: antora
  dependency-version: 3.2.0-alpha.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 12:17:55 -06:00
dependabot[bot] a07c67a365 Bump org.apache.maven:maven-resolver-provider from 3.9.15 to 3.9.16
Bumps org.apache.maven:maven-resolver-provider from 3.9.15 to 3.9.16.

---
updated-dependencies:
- dependency-name: org.apache.maven:maven-resolver-provider
  dependency-version: 3.9.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 12:17:50 -06:00
dependabot[bot] 16cb219e4a Bump org-bouncycastle from 1.80 to 1.80.2
Bumps `org-bouncycastle` from 1.80 to 1.80.2.

Updates `org.bouncycastle:bcpkix-jdk18on` from 1.80 to 1.80.2
- [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

Updates `org.bouncycastle:bcprov-jdk18on` from 1.80 to 1.80.2
- [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html)
- [Commits](https://github.com/bcgit/bc-java/commits)

---
updated-dependencies:
- dependency-name: org.bouncycastle:bcpkix-jdk18on
  dependency-version: 1.80.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.bouncycastle:bcprov-jdk18on
  dependency-version: 1.80.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 12:10:16 -06:00
Josh Cummings 7c11ada795 Merge branch '7.0.x' 2026-05-21 10:48:51 -06:00
Josh Cummings 944149ba71 Merge branch '6.5.x' into 7.0.x 2026-05-21 10:37:30 -06:00
Josh Cummings 9b14465243 Align Assertions in Builder with Deprecated Constructor
The deprecated (introspectionUri, clientId, clientSecret) constructors
that the builders replaced explicitly asserted non-null clientId and
clientSecret. Bring the builder's build() in line with that contract by
asserting at the API boundary rather than relying on downstream classes
to enforce it.

Closes gh-19201

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-05-21 10:36:10 -06:00
dependabot[bot] aaaa9b98ec Bump org.apereo.cas.client:cas-client-core from 4.1.0 to 4.1.1
Bumps [org.apereo.cas.client:cas-client-core](https://github.com/apereo/java-cas-client) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/apereo/java-cas-client/releases)
- [Commits](https://github.com/apereo/java-cas-client/compare/cas-client-4.1.0...cas-client-4.1.1)

---
updated-dependencies:
- dependency-name: org.apereo.cas.client:cas-client-core
  dependency-version: 4.1.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-21 03:18:55 +00:00
dependabot[bot] 43fe92911e Bump org.junit:junit-bom from 6.0.3 to 6.1.0
Bumps [org.junit:junit-bom](https://github.com/junit-team/junit-framework) from 6.0.3 to 6.1.0.
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r6.0.3...r6.1.0)

---
updated-dependencies:
- dependency-name: org.junit:junit-bom
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 07:42:34 +00:00
dependabot[bot] 5cc2f2e216 Bump org.hibernate.orm:hibernate-core from 7.3.4.Final to 7.3.5.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 7.3.4.Final to 7.3.5.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.3.5/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/7.3.4...7.3.5)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 7.3.5.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-18 03:44:35 +00:00
dependabot[bot] 4b5607dbce Bump org.apache.maven:maven-resolver-provider from 3.9.15 to 3.9.16
Bumps org.apache.maven:maven-resolver-provider from 3.9.15 to 3.9.16.

---
updated-dependencies:
- dependency-name: org.apache.maven:maven-resolver-provider
  dependency-version: 3.9.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-18 03:43:40 +00:00
dependabot[bot] 3c37353475 Bump antora from 3.2.0-alpha.11 to 3.2.0-alpha.12 in /docs
---
updated-dependencies:
- dependency-name: antora
  dependency-version: 3.2.0-alpha.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-15 00:46:45 +00:00
dependabot[bot] ee79eb69a5 Bump spring-io/spring-release-actions from 0.0.4 to 0.0.5
Bumps [spring-io/spring-release-actions](https://github.com/spring-io/spring-release-actions) from 0.0.4 to 0.0.5.
- [Release notes](https://github.com/spring-io/spring-release-actions/releases)
- [Commits](https://github.com/spring-io/spring-release-actions/compare/2420148725bebe44bd59a575a9b1961ca4459b0b...a1f321783a0769dd2aea4fad6c2ae2f95a52b885)

---
updated-dependencies:
- dependency-name: spring-io/spring-release-actions
  dependency-version: 0.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-15 00:46:33 +00:00
dependabot[bot] 0860432d5e Bump org-opensaml5 from 5.2.1 to 5.2.2
Bumps `org-opensaml5` from 5.2.1 to 5.2.2.

Updates `org.opensaml:opensaml-saml-api` from 5.2.1 to 5.2.2

Updates `org.opensaml:opensaml-saml-impl` from 5.2.1 to 5.2.2

---
updated-dependencies:
- dependency-name: org.opensaml:opensaml-saml-api
  dependency-version: 5.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.opensaml:opensaml-saml-impl
  dependency-version: 5.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-14 03:18:44 +00:00
dependabot[bot] 0fa8397a52 Bump gradle-wrapper from 9.5.0 to 9.5.1
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.5.0 to 9.5.1.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.5.0...v9.5.1)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.5.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-13 03:19:57 +00:00
dependabot[bot] eec789ff67 Bump org.slf4j:slf4j-api from 2.0.17 to 2.0.18
Bumps org.slf4j:slf4j-api from 2.0.17 to 2.0.18.

---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-api
  dependency-version: 2.0.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-13 03:17:44 +00:00
dependabot[bot] ee35c2c9cc Bump org.jetbrains.kotlinx:kotlinx-coroutines-bom from 1.10.2 to 1.11.0
Bumps [org.jetbrains.kotlinx:kotlinx-coroutines-bom](https://github.com/Kotlin/kotlinx.coroutines) from 1.10.2 to 1.11.0.
- [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases)
- [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md)
- [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.10.2...1.11.0)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-bom
  dependency-version: 1.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-11 03:20:13 +00:00
dependabot[bot] b0e9c34fcd Bump org.hibernate.orm:hibernate-core from 7.3.3.Final to 7.3.4.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 7.3.3.Final to 7.3.4.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.3.4/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/7.3.3...7.3.4)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 7.3.4.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-11 03:20:05 +00:00
dependabot[bot] 8c4c5fe91f Bump org.hibernate.orm:hibernate-core from 7.3.2.Final to 7.3.3.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 7.3.2.Final to 7.3.3.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.3.3/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/7.3.2...7.3.3)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 7.3.3.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-04 03:18:13 +00:00
dependabot[bot] 7dcd6f8f28 Bump tools.jackson:jackson-bom from 3.1.2 to 3.1.3
Bumps [tools.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 3.1.2 to 3.1.3.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-3.1.2...jackson-bom-3.1.3)

---
updated-dependencies:
- dependency-name: tools.jackson:jackson-bom
  dependency-version: 3.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-04 03:18:05 +00:00
dependabot[bot] f7b14479f3 Bump com.webauthn4j:webauthn4j-core
Bumps [com.webauthn4j:webauthn4j-core](https://github.com/webauthn4j/webauthn4j) from 0.31.3.RELEASE to 0.31.5.RELEASE.
- [Release notes](https://github.com/webauthn4j/webauthn4j/releases)
- [Commits](https://github.com/webauthn4j/webauthn4j/compare/0.31.3.RELEASE...0.31.5.RELEASE)

---
updated-dependencies:
- dependency-name: com.webauthn4j:webauthn4j-core
  dependency-version: 0.31.5.RELEASE
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-04 03:17:51 +00:00
Josh Cummings 65abe239ba Merge branch '7.0.x' 2026-04-29 09:04:18 -06:00
Josh Cummings fdc0fd23df Merge branch '6.5.x' into 7.0.x
Closes gh-19137
2026-04-29 09:03:43 -06:00
Josh Cummings 5b8d96f484 Merge remote-tracking branch 'origin/7.0.x' 2026-04-29 09:01:49 -06:00
Josh Cummings 931dfbe8e7 Merge remote-tracking branch 'origin/6.5.x' into 7.0.x 2026-04-29 09:01:34 -06:00
Josh Cummings b075f0df02 Decode percent-encoded values
Closes gh-19136

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-04-29 08:57:16 -06:00
dependabot[bot] 3c8887285d Bump gradle-wrapper from 9.4.1 to 9.5.0
Bumps [gradle-wrapper](https://github.com/gradle/gradle) from 9.4.1 to 9.5.0.
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.4.1...v9.5.0)

---
updated-dependencies:
- dependency-name: gradle-wrapper
  dependency-version: 9.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-29 03:20:03 +00:00
dependabot[bot] d184db85b2 Bump com.fasterxml.jackson:jackson-bom from 2.21.2 to 2.21.3
Bumps [com.fasterxml.jackson:jackson-bom](https://github.com/FasterXML/jackson-bom) from 2.21.2 to 2.21.3.
- [Commits](https://github.com/FasterXML/jackson-bom/compare/jackson-bom-2.21.2...jackson-bom-2.21.3)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson:jackson-bom
  dependency-version: 2.21.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-29 03:18:17 +00:00
dependabot[bot] 3924260e7c Bump org-jetbrains-kotlin from 2.3.20 to 2.3.21
Bumps `org-jetbrains-kotlin` from 2.3.20 to 2.3.21.

Updates `org.jetbrains.kotlin:kotlin-bom` from 2.3.20 to 2.3.21
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.3.20...v2.3.21)

Updates `org.jetbrains.kotlin:kotlin-gradle-plugin` from 2.3.20 to 2.3.21
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.3.20...v2.3.21)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-bom
  dependency-version: 2.3.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-version: 2.3.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-24 03:20:35 +00:00
dependabot[bot] 06336a62d0 Bump com.google.code.gson:gson from 2.13.2 to 2.14.0
Bumps [com.google.code.gson:gson](https://github.com/google/gson) from 2.13.2 to 2.14.0.
- [Release notes](https://github.com/google/gson/releases)
- [Changelog](https://github.com/google/gson/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/gson/compare/gson-parent-2.13.2...gson-parent-2.14.0)

---
updated-dependencies:
- dependency-name: com.google.code.gson:gson
  dependency-version: 2.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-24 03:15:52 +00:00
dependabot[bot] b932571913 Bump org.hibernate.orm:hibernate-core from 7.3.1.Final to 7.3.2.Final
Bumps [org.hibernate.orm:hibernate-core](https://github.com/hibernate/hibernate-orm) from 7.3.1.Final to 7.3.2.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/7.3.2/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/7.3.1...7.3.2)

---
updated-dependencies:
- dependency-name: org.hibernate.orm:hibernate-core
  dependency-version: 7.3.2.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-21 03:15:36 +00:00
github-actions[bot] 8720a282ce Next development version 2026-04-20 20:32:11 +00:00
github-actions[bot] fffc992a34 Next development version 2026-04-20 20:20:07 +00:00
Josh Cummings 53bc6a7796 Fix Formatting
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-04-20 13:52:41 -06:00
Josh Cummings aed95aa2b1 Fix Formatting
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-04-20 13:45:45 -06:00
github-actions[bot] 6343002b32 Next development version 2026-04-20 18:55:26 +00:00
github-actions[bot] 7b57a4a3bb Release 7.0.5 2026-04-20 17:54:57 +00:00
github-actions[bot] 0a9d4dc8fc Release 6.5.10 2026-04-20 17:54:21 +00:00
100 changed files with 2060 additions and 792 deletions
+3
View File
@@ -14,6 +14,7 @@ updates:
timezone: Etc/UTC
labels:
- 'type: dependency-upgrade'
- 'in: build'
registries:
- shibboleth
ignore:
@@ -60,6 +61,7 @@ updates:
timezone: Etc/UTC
labels:
- 'type: dependency-upgrade'
- 'in: build'
registries:
- shibboleth
ignore:
@@ -111,6 +113,7 @@ updates:
timezone: Etc/UTC
labels:
- 'type: dependency-upgrade'
- 'in: build'
registries:
- shibboleth
ignore:
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
toolchain: 25
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.0.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ --stacktrace
test-args: --refresh-dependencies -PforceMavenRepositories=snapshot,https://oss.sonatype.org/content/repositories/snapshots -PisOverrideVersionCatalog -PtestToolchain=${{ matrix.toolchain }} -PspringFrameworkVersion=7.0.+ -PreactorVersion=2025.+ -PspringDataVersion=2025.+ -PmicrometerVersion=1.+ --stacktrace
secrets: inherit
send-notification:
name: Send Notification
@@ -29,7 +29,7 @@ jobs:
secrets: inherit
deploy-artifacts:
name: Deploy Artifacts
needs: [ build]
needs: [ build ]
uses: spring-io/spring-security-release-tools/.github/workflows/deploy-artifacts.yml@b92832ecbc7cbe969201e6beafbde0ee400cf095 # v1.0.15
with:
should-deploy-artifacts: ${{ needs.build.outputs.should-deploy-artifacts }}
+5 -5
View File
@@ -15,24 +15,24 @@ jobs:
issues: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Compute Version
id: compute-version
uses: spring-io/spring-release-actions/compute-version@2420148725bebe44bd59a575a9b1961ca4459b0b # 0.0.4
uses: spring-io/spring-release-actions/compute-version@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5
- name: Get Today's Release Version
id: todays-release
uses: spring-io/spring-release-actions/get-todays-release-version@2420148725bebe44bd59a575a9b1961ca4459b0b # 0.0.4
uses: spring-io/spring-release-actions/get-todays-release-version@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5
with:
snapshot-version: ${{ steps.compute-version.outputs.version }}
milestone-repository: ${{ github.repository }}
milestone-token: ${{ secrets.GITHUB_TOKEN }}
- name: Compute Next Version
id: next-version
uses: spring-io/spring-release-actions/compute-next-version@2420148725bebe44bd59a575a9b1961ca4459b0b # 0.0.4
uses: spring-io/spring-release-actions/compute-next-version@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5
with:
version: ${{ steps.todays-release.outputs.release-version }}
- name: Schedule Next Milestone
uses: spring-io/spring-release-actions/schedule-milestone@2420148725bebe44bd59a575a9b1961ca4459b0b # 0.0.4
uses: spring-io/spring-release-actions/schedule-milestone@a1f321783a0769dd2aea4fad6c2ae2f95a52b885 # 0.0.5
with:
version: ${{ steps.next-version.outputs.version }}
version-date: ${{ steps.next-version.outputs.version-date }}
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
if: github.repository_owner == 'spring-projects'
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: docs-build
fetch-depth: 1
@@ -20,9 +20,9 @@ jobs:
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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up JDK 25
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
with:
java-version: '25'
distribution: 'temurin'
+2 -2
View File
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@c8668747d7c264864c8c7f7026d0d277d14a78dc # v2.0.6
with:
@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@c8668747d7c264864c8c7f7026d0d277d14a78dc # v2.0.6
with:
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 1
- name: Dispatch
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -2033,7 +2033,9 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
*/
public HttpSecurity securityMatcher(String... patterns) {
List<RequestMatcher> matchers = new ArrayList<>();
PathPatternRequestMatcher.Builder builder = getSharedObject(PathPatternRequestMatcher.Builder.class);
ApplicationContext context = getSharedObject(ApplicationContext.class);
PathPatternRequestMatcher.Builder builder = context.getBeanProvider(PathPatternRequestMatcher.Builder.class)
.getIfUnique(() -> getSharedObject(PathPatternRequestMatcher.Builder.class));
for (String pattern : patterns) {
matchers.add(builder.matcher(pattern));
}
@@ -239,9 +239,7 @@ class HttpSecurityConfiguration {
Map<Class<?>, Object> sharedObjects = new HashMap<>();
sharedObjects.put(ApplicationContext.class, this.context);
sharedObjects.put(ContentNegotiationStrategy.class, this.contentNegotiationStrategy);
sharedObjects.put(PathPatternRequestMatcher.Builder.class,
this.context.getBeanProvider(PathPatternRequestMatcher.Builder.class)
.getIfUnique(() -> constructRequestMatcherBuilder(this.context)));
sharedObjects.put(PathPatternRequestMatcher.Builder.class, constructRequestMatcherBuilder(this.context));
return sharedObjects;
}
@@ -304,8 +304,7 @@ class SpringSecurityCoreVersionSerializableTests {
private static String getCurrentVersion() {
String version = System.getProperty("springSecurityVersion");
String[] parts = version.split("\\.");
parts[2] = "x";
return String.join(".", parts);
return parts[0] + "." + parts[1] + ".x";
}
private static String getPreviousVersion() {
@@ -318,8 +317,7 @@ class SpringSecurityCoreVersionSerializableTests {
parts[0] = String.valueOf(Integer.parseInt(parts[0]) - 1);
parts[1] = "5"; // FIXME: this should not be hard coded
}
parts[2] = "x";
return String.join(".", parts);
return parts[0] + "." + parts[1] + ".x";
}
}
@@ -37,6 +37,7 @@ import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@@ -110,6 +111,21 @@ public class WebSecurityTests {
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_BAD_REQUEST);
}
// gh-19128
@Test
public void ignoringWhenBuilderBeanWithBasePathThenHonorsBasePath() throws Exception {
loadConfig(IgnoringBuilderBeanConfig.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_OK);
setup();
this.request.setServletPath("");
this.request.setRequestURI("/path");
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);
@@ -201,6 +217,52 @@ public class WebSecurityTests {
}
// gh-19128
@EnableWebSecurity
@Configuration
@EnableWebMvc
static class IgnoringBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/path");
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic(withDefaults())
.authorizeHttpRequests((requests) -> requests
.anyRequest().denyAll());
// @formatter:on
return http.build();
}
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(PasswordEncodedUser.user());
}
@RestController
static class PathController {
@RequestMapping("/path")
String path() {
return "path";
}
}
}
@Configuration
@EnableWebSecurity
static class RequestRejectedHandlerConfig {
@@ -40,6 +40,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
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.config.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.core.authority.FactorGrantedAuthority;
import org.springframework.security.core.context.SecurityContextChangedListener;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -153,6 +154,17 @@ public class FormLoginConfigurerTests {
// @formatter:on
}
// gh-19128
@Test
public void loginWhenBuilderBeanWithBasePathThenLoginProcessingUrlIgnoresBasePath() throws Exception {
this.spring.register(FormLoginBuilderBeanConfig.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();
@@ -519,6 +531,37 @@ public class FormLoginConfigurerTests {
}
// gh-19128
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class FormLoginBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated())
.formLogin(withDefaults());
// @formatter:on
return http.build();
}
@Bean
UserDetailsService userDetailsService() {
return new InMemoryUserDetailsManager(PasswordEncodedUser.user());
}
}
@Configuration
@EnableWebSecurity
static class FormLoginInLambdaConfig {
@@ -35,6 +35,7 @@ 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.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.UserDetailsService;
@@ -127,6 +128,17 @@ public class LogoutConfigurerTests {
// @formatter:on
}
// gh-19128
@Test
public void logoutWhenBuilderBeanWithBasePathThenLogoutUrlIgnoresBasePath() throws Exception {
this.spring.register(LogoutBuilderBeanConfig.class).autowire();
// @formatter:off
this.mvc.perform(post("/logout").with(csrf()))
.andExpect(status().isFound())
.andExpect(redirectedUrl("/login?logout"));
// @formatter:on
}
// SEC-2311
@Test
public void logoutWhenGetRequestAndCsrfDisabledThenRedirectsToLogin() throws Exception {
@@ -524,6 +536,29 @@ public class LogoutConfigurerTests {
}
// gh-19128
@Configuration
@EnableWebSecurity
static class LogoutBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.logout(withDefaults());
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebSecurity
static class CsrfDisabledConfig {
@@ -26,6 +26,7 @@ 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.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
@@ -66,6 +67,16 @@ public class PasswordManagementConfigurerTests {
.andExpect(redirectedUrl("/custom-change-password-page"));
}
// gh-19128
@Test
public void changePasswordWhenBuilderBeanWithBasePathThenChangePasswordUrlIgnoresBasePath() throws Exception {
this.spring.register(PasswordManagementBuilderBeanConfig.class).autowire();
this.mvc.perform(get("/.well-known/change-password"))
.andExpect(status().isFound())
.andExpect(redirectedUrl("/change-password"));
}
@Test
public void whenSettingNullChangePasswordPage() {
PasswordManagementConfigurer configurer = new PasswordManagementConfigurer();
@@ -102,6 +113,29 @@ public class PasswordManagementConfigurerTests {
}
// gh-19128
@Configuration
@EnableWebSecurity
static class PasswordManagementBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
return http
.passwordManagement(withDefaults())
.build();
// @formatter:on
}
}
@Configuration
@EnableWebSecurity
static class PasswordManagementWithCustomChangePasswordPageConfig {
@@ -34,6 +34,7 @@ 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.core.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.test.web.servlet.RequestCacheResultMatcher;
@@ -185,6 +186,21 @@ public class RequestCacheConfigurerTests {
this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/"));
}
// gh-19128
@Test
public void getWhenBuilderBeanWithBasePathThenSavedRequestMatcherIgnoresBasePath() throws Exception {
this.spring.register(RequestCacheBuilderBeanConfig.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 getWhenBookmarkedRequestIsAllMediaTypeThenPostAuthenticationRemembers() throws Exception {
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
@@ -401,6 +417,31 @@ public class RequestCacheConfigurerTests {
}
// gh-19128
@Configuration
@EnableWebSecurity
static class RequestCacheBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((requests) -> requests
.anyRequest().authenticated())
.formLogin(withDefaults());
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebSecurity
static class RequestCacheDisabledConfig {
@@ -56,6 +56,7 @@ import org.springframework.security.config.annotation.web.configurers.oauth2.cli
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.web.PathPatternRequestMatcherBuilderFactoryBean;
import org.springframework.security.context.DelegatingApplicationListener;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
@@ -440,6 +441,18 @@ public class OAuth2LoginConfigurerTests {
then(redirectStrategy).should().sendRedirect(any(), any(), anyString());
}
// gh-19128
@Test
public void oauth2LoginWhenBuilderBeanWithBasePathThenLoginProcessingUrlIgnoresBasePath() throws Exception {
loadConfig(OAuth2LoginBuilderBeanConfig.class);
String requestUri = "/login/oauth2/code/google";
this.request = get(requestUri).build();
this.request.setParameter("code", "code123");
this.request.setParameter("state", "state123");
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
assertThat(this.response.getRedirectedUrl()).endsWith("/login?error");
}
// gh-5347
@Test
public void oauth2LoginWithOneClientConfiguredThenRedirectForAuthorization() throws Exception {
@@ -788,6 +801,31 @@ public class OAuth2LoginConfigurerTests {
}
// gh-19128
@Configuration
@EnableWebSecurity
static class OAuth2LoginBuilderBeanConfig extends CommonSecurityFilterChainConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@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);
}
}
@Configuration
@EnableWebSecurity
static class OAuth2LoginConfigFormLogin extends CommonSecurityFilterChainConfig {
@@ -40,6 +40,7 @@ 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.core.userdetails.PasswordEncodedUser;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@@ -98,6 +99,19 @@ public class OneTimeTokenLoginConfigurerTests {
.andExpectAll(status().isFound(), redirectedUrl("/"), authenticated());
}
// gh-19128
@Test
void oneTimeTokenWhenBuilderBeanWithBasePathThenGenerateAndLoginUrlsIgnoreBasePath() throws Exception {
this.spring.register(OneTimeTokenBuilderBeanConfig.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();
@@ -296,6 +310,41 @@ public class OneTimeTokenLoginConfigurerTests {
}
// gh-19128
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@Import(UserDetailsServiceConfig.class)
static class OneTimeTokenBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@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)
@@ -57,6 +57,7 @@ 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.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@@ -244,6 +245,14 @@ public class Saml2LoginConfigurerTests {
performSaml2Login("ROLE_AUTH_MANAGER");
}
// gh-19128
@Test
public void saml2LoginWhenBuilderBeanWithBasePathThenAuthenticateUriIgnoresBasePath() throws Exception {
this.spring.register(Saml2LoginBuilderBeanConfig.class).autowire();
MvcResult result = this.mvc.perform(get("/saml2/authenticate/registration-id")).andReturn();
assertThat(result.getResponse().getRedirectedUrl()).contains("SAMLRequest");
}
@Test
public void authenticationRequestWhenAuthenticationRequestResolverBeanThenUses() throws Exception {
this.spring.register(CustomAuthenticationRequestResolverBean.class).autowire();
@@ -522,6 +531,33 @@ public class Saml2LoginConfigurerTests {
}
// gh-19128
@Configuration
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class Saml2LoginBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated()
)
.saml2Login(Customizer.withDefaults());
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
@@ -49,6 +49,7 @@ 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.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.saml2.core.Saml2Utils;
@@ -196,6 +197,17 @@ public class Saml2LogoutConfigurerTests {
verify(logoutHandler).logout(any(), any(), any());
}
// gh-19128
@Test
public void saml2LogoutWhenBuilderBeanWithBasePathThenLogoutUrlIgnoresBasePath() throws Exception {
this.spring.register(Saml2LogoutBuilderBeanConfig.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");
assertThat(location).startsWith("https://ap.example.org/logout/saml2/request");
}
@Test
public void saml2LogoutWhenUnauthenticatedThenEntryPoint() throws Exception {
this.spring.register(Saml2LogoutDefaultsConfig.class).autowire();
@@ -579,6 +591,32 @@ public class Saml2LogoutConfigurerTests {
}
// gh-19128
@Configuration
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
static class Saml2LogoutBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.saml2Login(withDefaults())
.saml2Logout(withDefaults());
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebSecurity
@Import(Saml2LoginConfigBeans.class)
@@ -30,6 +30,7 @@ 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.saml2.provider.service.metadata.OpenSaml5MetadataResolver;
import org.springframework.security.saml2.provider.service.metadata.RequestMatcherMetadataResponseResolver;
import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponse;
@@ -124,6 +125,15 @@ public class Saml2MetadataConfigurerTests {
this.mvc.perform(get("/saml2/metadata")).andExpect(status().isForbidden());
}
// gh-19128
@Test
void saml2MetadataWhenBuilderBeanWithBasePathThenMetadataUrlIgnoresBasePath() throws Exception {
this.spring.register(MetadataBuilderBeanConfig.class).autowire();
this.mvc.perform(get("/saml/metadata"))
.andExpect(status().isOk())
.andExpect(content().string(containsString("md:EntityDescriptor")));
}
@EnableWebSecurity
@Configuration
@Import(RelyingPartyRegistrationConfig.class)
@@ -141,6 +151,31 @@ public class Saml2MetadataConfigurerTests {
}
// gh-19128
@EnableWebSecurity
@Configuration
@Import(RelyingPartyRegistrationConfig.class)
static class MetadataBuilderBeanConfig {
@Bean
PathPatternRequestMatcherBuilderFactoryBean requestMatcherBuilder() {
PathPatternRequestMatcherBuilderFactoryBean bean = new PathPatternRequestMatcherBuilderFactoryBean();
bean.setBasePath("/spring");
return bean;
}
@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
}
}
@EnableWebSecurity
@Configuration
@Import(RelyingPartyRegistrationConfig.class)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
+3
View File
@@ -12,13 +12,16 @@ dependencies {
def springFrameworkVersion = project.property("springFrameworkVersion")
def reactorVersion = project.property("reactorVersion")
def springDataVersion = project.property("springDataVersion")
def micrometerVersion = project.property("micrometerVersion")
api platform("org.springframework:spring-framework-bom:$springFrameworkVersion")
api platform("io.projectreactor:reactor-bom:$reactorVersion")
api platform("org.springframework.data:spring-data-bom:$springDataVersion")
api platform("io.micrometer:micrometer-bom:$micrometerVersion")
} else {
api platform(libs.org.springframework.spring.framework.bom)
api platform(libs.io.projectreactor.reactor.bom)
api platform(libs.org.springframework.data.spring.data.bom)
api platform(libs.io.micrometer.micrometer.bom)
}
api platform(libs.io.rsocket.rsocket.bom)
api platform(libs.org.junit.junit.bom)
@@ -81,6 +81,12 @@ Xml::
and no authorization changes are necessary since it simply adjusts the `LogoutFilter`.
[NOTE]
====
The URI passed to `logoutUrl` (and to `logoutSuccessUrl`) is matched and redirected to literally.
If you want either URI to live under your application servlet's base path, include that prefix explicitly — for example, `logoutUrl("/api/logout")`.
====
[[permit-logout-endpoints]]
However, if you stand up your own logout success endpoint (or in a rare case, <<creating-custom-logout-endpoint, your own logout endpoint>>), say using {spring-framework-reference-url}web.html#spring-web[Spring MVC], you will need to permit it in Spring Security.
This is because Spring MVC processes your request after Spring Security does.
@@ -252,6 +252,13 @@ class MagicLinkOneTimeTokenGenerationSuccessHandler : OneTimeTokenGenerationSucc
----
======
[NOTE]
====
The URI passed to `generateTokenUrl` is matched literally.
If you want it to live under your application servlet's base path, include that prefix explicitly — for example, `generateTokenUrl("/api/ott/generate")`.
The same applies to `loginProcessingUrl` and `tokenGeneratingUrl` elsewhere on this page.
====
[[changing-submit-page-url]]
== Changing the Default Submit Page URL
@@ -155,6 +155,15 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
----
======
[NOTE]
====
The URI passed to `loginPage` (and to `loginProcessingUrl`, if set separately) is matched and redirected to literally.
If your custom login page is served by an application servlet mapped under a base path, include that prefix in the URI you pass to the DSL.
For example, if your dispatcher is at `/api/*`:
include-code::./FormLoginServletPathConfiguration[tag=loginPage,indent=0]
====
[[servlet-authentication-form-custom-html]]
When the login page is specified in the Spring Security configuration, you are responsible for rendering the page.
// FIXME: default login page rendered by Spring Security
@@ -642,6 +642,8 @@ This is because Spring Security requires all URIs to be absolute (minus the cont
There are several other components that create request matchers for you like {spring-boot-api-url}org/springframework/boot/security/autoconfigure/web/servlet/PathRequest.html[`PathRequest#toStaticResources#atCommonLocations`]
=====
If most of your authorization rules sit under the same servlet path, you can xref:servlet/integrations/mvc.adoc#mvc-requestmatcher[publish a `PathPatternRequestMatcher.Builder` bean] with that base path; Spring Security then applies it to the string overloads of `authorizeHttpRequests((authorize) -> authorize.requestMatchers(...))`, `HttpSecurity#securityMatcher(...)` / `#securityMatchers(...)`, and `WebSecurityCustomizer#ignoring().requestMatchers(...)`.
[[match-by-custom]]
=== Using a Custom Matcher
+2 -2
View File
@@ -1,10 +1,10 @@
{
"dependencies": {
"antora": "3.2.0-alpha.11",
"antora": "3.2.0-rc.2",
"@antora/atlas-extension": "1.0.0-alpha.5",
"@antora/collector-extension": "1.0.3",
"@asciidoctor/tabs": "1.0.0-beta.6",
"@springio/antora-extensions": "1.14.11",
"@springio/antora-extensions": "1.14.12",
"@springio/asciidoctor-extensions": "1.0.0-alpha.18"
}
}
+4
View File
@@ -12,6 +12,10 @@ plugins {
apply plugin: 'io.spring.convention.docs'
apply plugin: 'java'
node {
version = "24.15.0"
}
antora {
options = [clean: true, fetch: !project.gradle.startParameter.offline, stacktrace: true]
environment = [
@@ -0,0 +1,46 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.docs.servlet.authentication.passwords.servletauthenticationform;
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.web.SecurityFilterChain;
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
public class FormLoginServletPathConfiguration {
// tag::loginPage[]
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin((form) -> form
.loginPage("/api/login")
.loginProcessingUrl("/api/login")
);
// @formatter:on
return http.build();
}
// end::loginPage[]
}
@@ -1,5 +1,5 @@
/*
* Copyright 2026-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2026-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2026-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2026-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2026-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2026-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2026-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2026-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2026-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -0,0 +1,32 @@
package org.springframework.security.kt.docs.servlet.authentication.passwords.servletauthenticationform
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.invoke
import org.springframework.security.web.SecurityFilterChain
@EnableWebSecurity
@Configuration(proxyBeanMethods = false)
class FormLoginServletPathConfiguration {
// tag::loginPage[]
@Bean
fun springSecurity(http: HttpSecurity): SecurityFilterChain {
// @formatter:off
http {
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
formLogin {
loginPage = "/api/login"
loginProcessingUrl = "/api/login"
}
}
return http.build()
// @formatter:on
}
// end::loginPage[]
}
+1 -1
View File
@@ -14,7 +14,7 @@
# limitations under the License.
#
springBootVersion=4.1.0-SNAPSHOT
version=7.1.0-RC1
version=7.1.1-SNAPSHOT
samplesBranch=main
org.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError
org.gradle.parallel=true
+29 -27
View File
@@ -1,5 +1,6 @@
[versions]
com-squareup-okhttp3 = "3.14.9"
io-micrometer = "1.17.0"
io-rsocket = "1.1.5"
io-spring-javaformat = "0.0.47"
io-spring-nohttp = "0.0.11"
@@ -9,29 +10,30 @@ org-aspectj = "1.9.25.1"
org-bouncycastle = "1.84"
org-eclipse-jetty = "11.0.26"
org-jetbrains-dokka = "2.2.0"
org-jetbrains-kotlin = "2.3.20"
org-jetbrains-kotlinx = "1.10.2"
org-jetbrains-kotlin = "2.4.0"
org-jetbrains-kotlinx = "1.11.0"
org-mockito = "5.23.0"
org-opensaml5 = "5.2.1"
org-springframework = "7.0.7"
org-opensaml5 = "5.2.3"
org-springframework = "7.0.8"
com-password4j = "1.8.4"
[libraries]
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.32"
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.21.2"
ch-qos-logback-logback-classic = "ch.qos.logback:logback-classic:1.5.38"
com-fasterxml-jackson-jackson-bom = "com.fasterxml.jackson:jackson-bom:2.22.1"
com-google-inject-guice = "com.google.inject:guice:3.0"
com-netflix-nebula-nebula-project-plugin = "com.netflix.nebula:nebula-project-plugin:8.2.0"
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:10.6"
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:11.37"
com-nimbusds-nimbus-jose-jwt = "com.nimbusds:nimbus-jose-jwt:10.9"
com-nimbusds-oauth2-oidc-sdk = "com.nimbusds:oauth2-oidc-sdk:11.37.2"
com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "com-squareup-okhttp3" }
com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" }
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:7.0.4"
com-unboundid-unboundid-ldapsdk = "com.unboundid:unboundid-ldapsdk:7.0.5"
com-jayway-jsonpath-json-path = "com.jayway.jsonpath:json-path:2.10.0"
commons-collections = "commons-collections:commons-collections:3.2.2"
io-micrometer-context-propagation = "io.micrometer:context-propagation:1.2.1"
io-micrometer-micrometer-observation = "io.micrometer:micrometer-observation:1.16.5"
io-mockk = "io.mockk:mockk:1.14.9"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.5"
io-micrometer-micrometer-bom = { module = "io.micrometer:micrometer-bom", version.ref = "io-micrometer" }
io-micrometer-micrometer-observation = { module = "io.micrometer:micrometer-observation", version.ref = "io-micrometer" }
io-mockk = "io.mockk:mockk:1.14.11"
io-projectreactor-reactor-bom = "io.projectreactor:reactor-bom:2025.0.6"
io-rsocket-rsocket-bom = { module = "io.rsocket:rsocket-bom", version.ref = "io-rsocket" }
io-spring-javaformat-spring-javaformat-checkstyle = { module = "io.spring.javaformat:spring-javaformat-checkstyle", version.ref = "io-spring-javaformat" }
io-spring-javaformat-spring-javaformat-gradle-plugin = { module = "io.spring.javaformat:spring-javaformat-gradle-plugin", version.ref = "io-spring-javaformat" }
@@ -50,13 +52,13 @@ jakarta-xml-bind-jakarta-xml-bind-api = "jakarta.xml.bind:jakarta.xml.bind-api:4
ldapsdk = "ldapsdk:ldapsdk:4.1"
net-sourceforge-htmlunit = "net.sourceforge.htmlunit:htmlunit:2.70.0"
org-htmlunit-htmlunit = "org.htmlunit:htmlunit:4.21.0"
org-apache-httpcomponents-httpclient = "org.apache.httpcomponents.client5:httpclient5:5.6.1"
org-apache-kerby-simplekdc='org.apache.kerby:kerb-simplekdc:2.1.1'
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.15"
org-apache-httpcomponents-httpclient = "org.apache.httpcomponents.client5:httpclient5:5.6.2"
org-apache-kerby-simplekdc='org.apache.kerby:kerb-simplekdc:2.1.2'
org-apache-maven-maven-resolver-provider = "org.apache.maven:maven-resolver-provider:3.9.16"
org-apache-maven-resolver-maven-resolver-connector-basic = { module = "org.apache.maven.resolver:maven-resolver-connector-basic", version.ref = "org-apache-maven-resolver" }
org-apache-maven-resolver-maven-resolver-impl = { module = "org.apache.maven.resolver:maven-resolver-impl", version.ref = "org-apache-maven-resolver" }
org-apache-maven-resolver-maven-resolver-transport-http = { module = "org.apache.maven.resolver:maven-resolver-transport-http", version.ref = "org-apache-maven-resolver" }
org-apereo-cas-client-cas-client-core = "org.apereo.cas.client:cas-client-core:4.1.0"
org-apereo-cas-client-cas-client-core = "org.apereo.cas.client:cas-client-core:4.1.1"
io-freefair-gradle-aspectj-plugin = "io.freefair.gradle:aspectj-plugin:8.14.4"
org-aspectj-aspectjrt = { module = "org.aspectj:aspectjrt", version.ref = "org-aspectj" }
org-aspectj-aspectjweaver = { module = "org.aspectj:aspectjweaver", version.ref = "org-aspectj" }
@@ -66,30 +68,30 @@ org-bouncycastle-bcprov-jdk15on = { module = "org.bouncycastle:bcprov-jdk18on",
org-eclipse-jetty-jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "org-eclipse-jetty" }
org-eclipse-jetty-jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet", version.ref = "org-eclipse-jetty" }
org-hamcrest = "org.hamcrest:hamcrest:2.2"
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:7.3.1.Final"
org-hibernate-orm-hibernate-core = "org.hibernate.orm:hibernate-core:7.4.4.Final"
org-hsqldb = "org.hsqldb:hsqldb:2.7.4"
org-jetbrains-dokka-dokka-gradle-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "org-jetbrains-dokka" }
org-jetbrains-kotlin-kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "org-jetbrains-kotlin" }
org-jetbrains-kotlin-kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "org-jetbrains-kotlin" }
org-jetbrains-kotlinx-kotlinx-coroutines-bom = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-bom", version.ref = "org-jetbrains-kotlinx" }
org-junit-junit-bom = "org.junit:junit-bom:6.0.3"
org-junit-junit-bom = "org.junit:junit-bom:6.1.1"
org-mockito-mockito-bom = { module = "org.mockito:mockito-bom", version.ref = "org-mockito" }
org-opensaml-opensaml5-saml-api = { module = "org.opensaml:opensaml-saml-api", version.ref = "org-opensaml5" }
org-opensaml-opensaml5-saml-impl = { module = "org.opensaml:opensaml-saml-impl", version.ref = "org-opensaml5" }
org-python-jython = { module = "org.python:jython", version = "2.5.3" }
org-seleniumhq-selenium-htmlunit-driver = "org.seleniumhq.selenium:htmlunit3-driver:4.43.0"
org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:4.43.0"
org-seleniumhq-selenium-htmlunit-driver = "org.seleniumhq.selenium:htmlunit3-driver:4.46.0"
org-seleniumhq-selenium-selenium-java = "org.seleniumhq.selenium:selenium-java:4.46.0"
org-seleniumhq-selenium-selenium-support = "org.seleniumhq.selenium:selenium-support:3.141.59"
org-skyscreamer-jsonassert = "org.skyscreamer:jsonassert:1.5.3"
org-slf4j-log4j-over-slf4j = "org.slf4j:log4j-over-slf4j:1.7.36"
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.17"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2025.1.5"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.0.3"
org-slf4j-slf4j-api = "org.slf4j:slf4j-api:2.0.18"
org-springframework-data-spring-data-bom = "org.springframework.data:spring-data-bom:2026.0.0"
org-springframework-ldap-spring-ldap-core = "org.springframework.ldap:spring-ldap-core:4.1.0"
org-springframework-spring-framework-bom = { module = "org.springframework:spring-framework-bom", version.ref = "org-springframework" }
org-synchronoss-cloud-nio-multipart-parser = "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.1.2"
tools-jackson-jackson-bom = "tools.jackson:jackson-bom:3.2.0"
com-google-code-gson-gson = "com.google.code.gson:gson:2.13.2"
com-google-code-gson-gson = "com.google.code.gson:gson:2.14.0"
com-thaiopensource-trag = "com.thaiopensource:trang:20091111"
net-sourceforge-saxon-saxon = "net.sourceforge.saxon:saxon:9.1.0.8"
org-yaml-snakeyaml = "org.yaml:snakeyaml:1.33"
@@ -103,8 +105,8 @@ org-jfrog-buildinfo-build-info-extractor-gradle = "org.jfrog.buildinfo:build-inf
org-sonarsource-scanner-gradle-sonarqube-gradle-plugin = "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.8.0.1969"
org-instancio-instancio-junit = "org.instancio:instancio-junit:3.7.1"
spring-nullability = 'io.spring.nullability:io.spring.nullability.gradle.plugin:0.0.13'
webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.31.3.RELEASE'
spring-nullability = 'io.spring.nullability:io.spring.nullability.gradle.plugin:0.0.14'
webauthn4j-core = 'com.webauthn4j:webauthn4j-core:0.31.8.RELEASE'
com-password4j-password4j = { module = "com.password4j:password4j", version.ref = "com-password4j" }
[plugins]
Binary file not shown.
+4 -2
View File
@@ -1,8 +1,10 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=2ab2958f2a1e51120c326cad6f385153bb11ee93b3c216c5fccebfdfbb7ec6cb
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
distributionSha256Sum=9c0f7faeeb306cb14e4279a3e084ca6b596894089a0638e68a07c945a32c9e14
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+3 -3
View File
@@ -20,7 +20,7 @@
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
# gradlew start up script for POSIX generated by Gradle.
#
# Important for running:
#
@@ -29,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
# ksh gradlew
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
Vendored
+12 -23
View File
@@ -19,12 +19,12 @@
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem gradlew startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@@ -51,7 +51,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
@@ -65,29 +65,18 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:execute
@rem Setup the command line
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
@rem Execute gradlew
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%
+248 -239
View File
@@ -12,7 +12,7 @@
"@eslint/js": "^9.6.0",
"@types/sinon": "^17.0.3",
"chai": "~4.3",
"esbuild": "^0.25.0",
"esbuild": "^0.28.1",
"eslint": "^9.6.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
@@ -27,14 +27,13 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
"integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
"integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
@@ -44,14 +43,13 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
"integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
"integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
@@ -61,14 +59,13 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
"integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
"integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
@@ -78,14 +75,13 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
"integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
"integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
@@ -95,14 +91,13 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
"integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
"integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -112,14 +107,13 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
"integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
"integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -129,14 +123,13 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
"integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
"integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
@@ -146,14 +139,13 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
"integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
"integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
@@ -163,14 +155,13 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
"integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
"integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -180,14 +171,13 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
"integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
"integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -197,14 +187,13 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
"integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
"integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -214,14 +203,13 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
"integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
"integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -231,14 +219,13 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
"integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
"integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -248,14 +235,13 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
"integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
"integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -265,14 +251,13 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
"integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
"integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -282,14 +267,13 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
"integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
"integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -299,14 +283,13 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
"integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
"integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -316,14 +299,13 @@
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
"integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
"integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
@@ -333,14 +315,13 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
"integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
"integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
@@ -350,14 +331,13 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
"integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
"integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
@@ -367,14 +347,13 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
"integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
"integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
@@ -383,15 +362,30 @@
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
"integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
"integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
"integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
@@ -401,14 +395,13 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
"integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
"integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
@@ -418,14 +411,13 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
"integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
"integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
@@ -435,14 +427,13 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
"integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
"integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
@@ -1233,12 +1224,11 @@
"dev": true
},
"node_modules/esbuild": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
"integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
"integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
@@ -1246,31 +1236,32 @@
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.0",
"@esbuild/android-arm": "0.25.0",
"@esbuild/android-arm64": "0.25.0",
"@esbuild/android-x64": "0.25.0",
"@esbuild/darwin-arm64": "0.25.0",
"@esbuild/darwin-x64": "0.25.0",
"@esbuild/freebsd-arm64": "0.25.0",
"@esbuild/freebsd-x64": "0.25.0",
"@esbuild/linux-arm": "0.25.0",
"@esbuild/linux-arm64": "0.25.0",
"@esbuild/linux-ia32": "0.25.0",
"@esbuild/linux-loong64": "0.25.0",
"@esbuild/linux-mips64el": "0.25.0",
"@esbuild/linux-ppc64": "0.25.0",
"@esbuild/linux-riscv64": "0.25.0",
"@esbuild/linux-s390x": "0.25.0",
"@esbuild/linux-x64": "0.25.0",
"@esbuild/netbsd-arm64": "0.25.0",
"@esbuild/netbsd-x64": "0.25.0",
"@esbuild/openbsd-arm64": "0.25.0",
"@esbuild/openbsd-x64": "0.25.0",
"@esbuild/sunos-x64": "0.25.0",
"@esbuild/win32-arm64": "0.25.0",
"@esbuild/win32-ia32": "0.25.0",
"@esbuild/win32-x64": "0.25.0"
"@esbuild/aix-ppc64": "0.28.1",
"@esbuild/android-arm": "0.28.1",
"@esbuild/android-arm64": "0.28.1",
"@esbuild/android-x64": "0.28.1",
"@esbuild/darwin-arm64": "0.28.1",
"@esbuild/darwin-x64": "0.28.1",
"@esbuild/freebsd-arm64": "0.28.1",
"@esbuild/freebsd-x64": "0.28.1",
"@esbuild/linux-arm": "0.28.1",
"@esbuild/linux-arm64": "0.28.1",
"@esbuild/linux-ia32": "0.28.1",
"@esbuild/linux-loong64": "0.28.1",
"@esbuild/linux-mips64el": "0.28.1",
"@esbuild/linux-ppc64": "0.28.1",
"@esbuild/linux-riscv64": "0.28.1",
"@esbuild/linux-s390x": "0.28.1",
"@esbuild/linux-x64": "0.28.1",
"@esbuild/netbsd-arm64": "0.28.1",
"@esbuild/netbsd-x64": "0.28.1",
"@esbuild/openbsd-arm64": "0.28.1",
"@esbuild/openbsd-x64": "0.28.1",
"@esbuild/openharmony-arm64": "0.28.1",
"@esbuild/sunos-x64": "0.28.1",
"@esbuild/win32-arm64": "0.28.1",
"@esbuild/win32-ia32": "0.28.1",
"@esbuild/win32-x64": "0.28.1"
}
},
"node_modules/escalade": {
@@ -1944,10 +1935,20 @@
"dev": true
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
"integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"dependencies": {
"argparse": "^2.0.1"
},
@@ -3272,177 +3273,184 @@
},
"dependencies": {
"@esbuild/aix-ppc64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
"integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
"integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
"dev": true,
"optional": true
},
"@esbuild/android-arm": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
"integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
"integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
"dev": true,
"optional": true
},
"@esbuild/android-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
"integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
"integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
"dev": true,
"optional": true
},
"@esbuild/android-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
"integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
"integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
"dev": true,
"optional": true
},
"@esbuild/darwin-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
"integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
"integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
"dev": true,
"optional": true
},
"@esbuild/darwin-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
"integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
"integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
"dev": true,
"optional": true
},
"@esbuild/freebsd-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
"integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
"integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
"dev": true,
"optional": true
},
"@esbuild/freebsd-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
"integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
"integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-arm": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
"integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
"integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
"integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
"integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
"dev": true,
"optional": true
},
"@esbuild/linux-ia32": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
"integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
"integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
"dev": true,
"optional": true
},
"@esbuild/linux-loong64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
"integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
"integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
"dev": true,
"optional": true
},
"@esbuild/linux-mips64el": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
"integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
"integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-ppc64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
"integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
"integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-riscv64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
"integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
"integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
"dev": true,
"optional": true
},
"@esbuild/linux-s390x": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
"integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
"integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
"dev": true,
"optional": true
},
"@esbuild/linux-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
"integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
"integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
"dev": true,
"optional": true
},
"@esbuild/netbsd-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
"integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
"integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
"dev": true,
"optional": true
},
"@esbuild/netbsd-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
"integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
"integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
"dev": true,
"optional": true
},
"@esbuild/openbsd-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
"integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
"integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
"dev": true,
"optional": true
},
"@esbuild/openbsd-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
"integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
"integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
"dev": true,
"optional": true
},
"@esbuild/openharmony-arm64": {
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
"integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
"dev": true,
"optional": true
},
"@esbuild/sunos-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
"integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
"integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
"dev": true,
"optional": true
},
"@esbuild/win32-arm64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
"integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
"integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
"dev": true,
"optional": true
},
"@esbuild/win32-ia32": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
"integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
"integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
"dev": true,
"optional": true
},
"@esbuild/win32-x64": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
"integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
"integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
"dev": true,
"optional": true
},
@@ -4019,36 +4027,37 @@
"dev": true
},
"esbuild": {
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
"integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
"version": "0.28.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
"integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
"dev": true,
"requires": {
"@esbuild/aix-ppc64": "0.25.0",
"@esbuild/android-arm": "0.25.0",
"@esbuild/android-arm64": "0.25.0",
"@esbuild/android-x64": "0.25.0",
"@esbuild/darwin-arm64": "0.25.0",
"@esbuild/darwin-x64": "0.25.0",
"@esbuild/freebsd-arm64": "0.25.0",
"@esbuild/freebsd-x64": "0.25.0",
"@esbuild/linux-arm": "0.25.0",
"@esbuild/linux-arm64": "0.25.0",
"@esbuild/linux-ia32": "0.25.0",
"@esbuild/linux-loong64": "0.25.0",
"@esbuild/linux-mips64el": "0.25.0",
"@esbuild/linux-ppc64": "0.25.0",
"@esbuild/linux-riscv64": "0.25.0",
"@esbuild/linux-s390x": "0.25.0",
"@esbuild/linux-x64": "0.25.0",
"@esbuild/netbsd-arm64": "0.25.0",
"@esbuild/netbsd-x64": "0.25.0",
"@esbuild/openbsd-arm64": "0.25.0",
"@esbuild/openbsd-x64": "0.25.0",
"@esbuild/sunos-x64": "0.25.0",
"@esbuild/win32-arm64": "0.25.0",
"@esbuild/win32-ia32": "0.25.0",
"@esbuild/win32-x64": "0.25.0"
"@esbuild/aix-ppc64": "0.28.1",
"@esbuild/android-arm": "0.28.1",
"@esbuild/android-arm64": "0.28.1",
"@esbuild/android-x64": "0.28.1",
"@esbuild/darwin-arm64": "0.28.1",
"@esbuild/darwin-x64": "0.28.1",
"@esbuild/freebsd-arm64": "0.28.1",
"@esbuild/freebsd-x64": "0.28.1",
"@esbuild/linux-arm": "0.28.1",
"@esbuild/linux-arm64": "0.28.1",
"@esbuild/linux-ia32": "0.28.1",
"@esbuild/linux-loong64": "0.28.1",
"@esbuild/linux-mips64el": "0.28.1",
"@esbuild/linux-ppc64": "0.28.1",
"@esbuild/linux-riscv64": "0.28.1",
"@esbuild/linux-s390x": "0.28.1",
"@esbuild/linux-x64": "0.28.1",
"@esbuild/netbsd-arm64": "0.28.1",
"@esbuild/netbsd-x64": "0.28.1",
"@esbuild/openbsd-arm64": "0.28.1",
"@esbuild/openbsd-x64": "0.28.1",
"@esbuild/openharmony-arm64": "0.28.1",
"@esbuild/sunos-x64": "0.28.1",
"@esbuild/win32-arm64": "0.28.1",
"@esbuild/win32-ia32": "0.28.1",
"@esbuild/win32-x64": "0.28.1"
}
},
"escalade": {
@@ -4523,9 +4532,9 @@
"dev": true
},
"js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
"integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
"dev": true,
"requires": {
"argparse": "^2.0.1"
+1 -1
View File
@@ -37,7 +37,7 @@
"@eslint/js": "^9.6.0",
"@types/sinon": "^17.0.3",
"chai": "~4.3",
"esbuild": "^0.25.0",
"esbuild": "^0.28.1",
"eslint": "^9.6.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
@@ -500,7 +500,8 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
registeredClient);
if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST)
&& (parameterName.equals(OAuth2ParameterNames.CLIENT_ID)
|| parameterName.equals(OAuth2ParameterNames.STATE))) {
|| parameterName.equals(OAuth2ParameterNames.STATE)
|| parameterName.equals(OAuth2ParameterNames.REQUEST_URI))) {
redirectUri = null; // Prevent redirects
}
@@ -298,8 +298,10 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationValidator
String redirectUri = StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())
? authorizationCodeRequestAuthentication.getRedirectUri()
: registeredClient.getRedirectUris().iterator().next();
if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST)
&& parameterName.equals(OAuth2ParameterNames.REDIRECT_URI)) {
if ((error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST)
|| error.getErrorCode().equals(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT))
&& (parameterName.equals(OAuth2ParameterNames.CLIENT_ID)
|| parameterName.equals(OAuth2ParameterNames.REDIRECT_URI))) {
redirectUri = null; // Prevent redirects
}
@@ -132,52 +132,66 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationConverter impleme
principal = ANONYMOUS_AUTHENTICATION;
}
// redirect_uri (OPTIONAL)
String redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI);
List<String> redirectUriParams = parameters.get(OAuth2ParameterNames.REDIRECT_URI);
if (StringUtils.hasText(redirectUri) && redirectUriParams != null && redirectUriParams.size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI);
String redirectUri = null;
if (!StringUtils.hasText(requestUri)) {
// redirect_uri (OPTIONAL)
redirectUri = parameters.getFirst(OAuth2ParameterNames.REDIRECT_URI);
List<String> redirectUriParams = parameters.get(OAuth2ParameterNames.REDIRECT_URI);
if (StringUtils.hasText(redirectUri) && redirectUriParams != null && redirectUriParams.size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI);
}
}
// scope (OPTIONAL)
Set<String> scopes = null;
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
List<String> scopeParams = parameters.get(OAuth2ParameterNames.SCOPE);
if (StringUtils.hasText(scope) && scopeParams != null && scopeParams.size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE);
}
if (StringUtils.hasText(scope)) {
scopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
if (!StringUtils.hasText(requestUri)) {
// scope (OPTIONAL)
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
List<String> scopeParams = parameters.get(OAuth2ParameterNames.SCOPE);
if (StringUtils.hasText(scope) && scopeParams != null && scopeParams.size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE);
}
if (StringUtils.hasText(scope)) {
scopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
}
}
// state (RECOMMENDED)
String state = parameters.getFirst(OAuth2ParameterNames.STATE);
List<String> stateParams = parameters.get(OAuth2ParameterNames.STATE);
if (StringUtils.hasText(state) && stateParams != null && stateParams.size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
String state = null;
if (!StringUtils.hasText(requestUri)) {
// state (RECOMMENDED)
state = parameters.getFirst(OAuth2ParameterNames.STATE);
List<String> stateParams = parameters.get(OAuth2ParameterNames.STATE);
if (StringUtils.hasText(state) && stateParams != null && stateParams.size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
}
}
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
String codeChallenge = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE);
List<String> codeChallengeParams = parameters.get(PkceParameterNames.CODE_CHALLENGE);
if (StringUtils.hasText(codeChallenge) && codeChallengeParams != null && codeChallengeParams.size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI);
if (!StringUtils.hasText(requestUri)) {
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
String codeChallenge = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE);
List<String> codeChallengeParams = parameters.get(PkceParameterNames.CODE_CHALLENGE);
if (StringUtils.hasText(codeChallenge) && codeChallengeParams != null && codeChallengeParams.size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE, PKCE_ERROR_URI);
}
}
// code_challenge_method (OPTIONAL for public clients) - RFC 7636 (PKCE)
String codeChallengeMethod = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE_METHOD);
List<String> codeChallengeMethodParams = parameters.get(PkceParameterNames.CODE_CHALLENGE_METHOD);
if (StringUtils.hasText(codeChallengeMethod) && codeChallengeMethodParams != null
&& codeChallengeMethodParams.size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI);
if (!StringUtils.hasText(requestUri)) {
// code_challenge_method (OPTIONAL for public clients) - RFC 7636 (PKCE)
String codeChallengeMethod = parameters.getFirst(PkceParameterNames.CODE_CHALLENGE_METHOD);
List<String> codeChallengeMethodParams = parameters.get(PkceParameterNames.CODE_CHALLENGE_METHOD);
if (StringUtils.hasText(codeChallengeMethod) && codeChallengeMethodParams != null
&& codeChallengeMethodParams.size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, PkceParameterNames.CODE_CHALLENGE_METHOD, PKCE_ERROR_URI);
}
}
// prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request)
if (!CollectionUtils.isEmpty(scopes) && scopes.contains(OidcScopes.OPENID)) {
String prompt = parameters.getFirst("prompt");
List<String> promptParams = parameters.get("prompt");
if (StringUtils.hasText(prompt) && promptParams != null && promptParams.size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, "prompt");
if (!StringUtils.hasText(requestUri)) {
// prompt (OPTIONAL for OpenID Connect 1.0 Authentication Request)
if (!CollectionUtils.isEmpty(scopes) && scopes.contains(OidcScopes.OPENID)) {
String prompt = parameters.getFirst("prompt");
List<String> promptParams = parameters.get("prompt");
if (StringUtils.hasText(prompt) && promptParams != null && promptParams.size() != 1) {
throwError(OAuth2ErrorCodes.INVALID_REQUEST, "prompt");
}
}
}
@@ -311,7 +311,7 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
assertThatExceptionOfType(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.satisfies((ex) -> assertAuthenticationException(ex, OAuth2ErrorCodes.UNAUTHORIZED_CLIENT,
OAuth2ParameterNames.CLIENT_ID, authentication.getRedirectUri()));
OAuth2ParameterNames.CLIENT_ID, null));
}
@Test
@@ -128,7 +128,7 @@ public class OAuth2PushedAuthorizationRequestAuthenticationProviderTests {
assertThatExceptionOfType(OAuth2AuthorizationCodeRequestAuthenticationException.class)
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
.satisfies((ex) -> assertAuthenticationException(ex, OAuth2ErrorCodes.UNAUTHORIZED_CLIENT,
OAuth2ParameterNames.CLIENT_ID, authentication.getRedirectUri()));
OAuth2ParameterNames.CLIENT_ID, null));
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2025-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2025-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
/*
* Copyright 2025-present the original author or authors.
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -380,11 +380,10 @@ public class SpringOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
* @since 6.5
*/
public SpringOpaqueTokenIntrospector build() {
Assert.notNull(this.clientId, "clientId cannot be null");
Assert.notNull(this.clientSecret, "clientSecret cannot be null");
RestTemplate restTemplate = new RestTemplate();
if (this.clientId != null && this.clientSecret != null) {
restTemplate.getInterceptors()
.add(new BasicAuthenticationInterceptor(this.clientId, this.clientSecret));
}
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(this.clientId, this.clientSecret));
SpringOpaqueTokenIntrospector introspector = new SpringOpaqueTokenIntrospector(this.introspectionUri,
restTemplate);
this.postProcessors.forEach((postProcessor) -> postProcessor.accept(introspector));
@@ -334,13 +334,13 @@ public class SpringReactiveOpaqueTokenIntrospector implements ReactiveOpaqueToke
* @since 6.5
*/
public SpringReactiveOpaqueTokenIntrospector build() {
WebClient.Builder builder = WebClient.builder();
if (this.clientId != null && this.clientSecret != null) {
String clientId = this.clientId;
String clientSecret = this.clientSecret;
builder.defaultHeaders((h) -> h.setBasicAuth(clientId, clientSecret));
}
WebClient webClient = builder.build();
Assert.notNull(this.clientId, "clientId cannot be null");
Assert.notNull(this.clientSecret, "clientSecret cannot be null");
String clientId = this.clientId;
String clientSecret = this.clientSecret;
WebClient webClient = WebClient.builder()
.defaultHeaders((h) -> h.setBasicAuth(clientId, clientSecret))
.build();
SpringReactiveOpaqueTokenIntrospector introspector = new SpringReactiveOpaqueTokenIntrospector(
this.introspectionUri, webClient);
this.postProcessors.forEach((postProcessor) -> postProcessor.accept(introspector));
@@ -402,6 +402,13 @@ public class SpringOpaqueTokenIntrospectorTests {
}
}
// gh-19201
@Test
public void builderWhenMissingClientCredentialsThenThrowsException() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> SpringOpaqueTokenIntrospector.withIntrospectionUri(INTROSPECTION_URL).build());
}
private static ResponseEntity<Map<String, Object>> response(String content) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
@@ -328,6 +328,13 @@ public class SpringReactiveOpaqueTokenIntrospectorTests {
}
}
// gh-19201
@Test
public void builderWhenMissingClientCredentialsThenThrowsException() {
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> SpringReactiveOpaqueTokenIntrospector.withIntrospectionUri(INTROSPECTION_URL).build());
}
private WebClient mockResponse(String response) {
return mockResponse(toMap(response));
}
@@ -148,6 +148,14 @@ tasks.register("opensaml5Test", Test) {
classpath = sourceSets.opensaml5Test.output + sourceSets.opensaml5Test.runtimeClasspath
}
tasks.named("test") {
dependsOn opensaml5Test
tasks.register("bouncyCastleTest", Test) {
useJUnitPlatform()
testClassesDirs = sourceSets.test.output.classesDirs
classpath = sourceSets.test.runtimeClasspath
include "**/JdbcAssertingPartyMetadataRepositoryBouncyCastleTests.class"
}
tasks.named("test") {
exclude "**/JdbcAssertingPartyMetadataRepositoryBouncyCastleTests.class"
dependsOn opensaml5Test, bouncyCastleTest
}
@@ -18,6 +18,7 @@ package org.springframework.security.saml2.internal;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
@@ -64,7 +65,7 @@ final class Saml2Utils {
static String samlInflate(byte[] b) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
InflaterOutputStream iout = new InflaterOutputStream(new CappedOutputStream(out), new Inflater(true));
iout.write(b);
iout.finish();
return new String(out.toByteArray(), StandardCharsets.UTF_8);
@@ -193,4 +194,27 @@ final class Saml2Utils {
}
static class CappedOutputStream extends OutputStream {
private static final long MAX_SIZE = 1024 * 1024;
private final OutputStream delegate;
private int size;
CappedOutputStream(OutputStream delegate) {
this.delegate = delegate;
}
@Override
public void write(int b) throws IOException {
if (this.size >= MAX_SIZE) {
throw new IOException("SAML payload exceeded maximum size of " + MAX_SIZE);
}
this.delegate.write(b);
this.size++;
}
}
}
@@ -331,62 +331,75 @@ class BaseOpenSamlAuthenticationProvider implements AuthenticationProvider {
boolean responseSigned = response.isSigned();
ResponseToken responseToken = new ResponseToken(response, token);
Saml2ResponseValidatorResult result = this.responseSignatureValidator.convert(responseToken);
Collection<Saml2Error> responseSignatureErrors = this.responseSignatureValidator.convert(responseToken)
.getErrors();
if (!responseSignatureErrors.isEmpty()) {
reportErrors(response, responseSignatureErrors);
return;
}
Collection<Saml2Error> errors = new ArrayList<>();
if (responseSigned) {
this.responseElementsDecrypter.accept(responseToken);
}
else if (!response.getEncryptedAssertions().isEmpty()) {
result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE,
"Did not decrypt response [" + response.getID() + "] since it is not signed"));
}
if (!this.validateResponseAfterAssertions) {
result = result.concat(this.responseValidator.convert(responseToken));
errors.addAll(this.responseValidator.convert(responseToken).getErrors());
}
boolean allAssertionsSigned = true;
for (Assertion assertion : response.getAssertions()) {
AssertionToken assertionToken = new AssertionToken(assertion, token);
result = result.concat(this.assertionSignatureValidator.convert(assertionToken));
Collection<Saml2Error> assertionSignatureErrors = this.assertionSignatureValidator.convert(assertionToken)
.getErrors();
errors.addAll(assertionSignatureErrors);
allAssertionsSigned = allAssertionsSigned && assertion.isSigned();
if (!assertionSignatureErrors.isEmpty()) {
continue;
}
if (responseSigned || assertion.isSigned()) {
this.assertionElementsDecrypter.accept(new AssertionToken(assertion, token));
}
result = result.concat(this.assertionValidator.convert(assertionToken));
errors.addAll(this.assertionValidator.convert(assertionToken).getErrors());
}
if (!responseSigned && !allAssertionsSigned) {
String description = "Either the response or one of the assertions is unsigned. "
+ "Please either sign the response or all of the assertions.";
result = result.concat(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, description));
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_SIGNATURE, description));
}
if (this.validateResponseAfterAssertions) {
result = result.concat(this.responseValidator.convert(responseToken));
errors.addAll(this.responseValidator.convert(responseToken).getErrors());
}
else {
Assertion firstAssertion = CollectionUtils.firstElement(response.getAssertions());
if (firstAssertion != null && !hasName(firstAssertion)) {
Saml2Error error = new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND,
"Assertion [" + firstAssertion.getID() + "] is missing a subject");
result = result.concat(error);
errors.add(new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND,
"Assertion [" + firstAssertion.getID() + "] is missing a subject"));
}
}
if (result.hasErrors()) {
Collection<Saml2Error> errors = result.getErrors();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Found " + errors.size() + " validation errors in SAML response [" + response.getID()
+ "]: " + errors);
}
else if (this.logger.isDebugEnabled()) {
this.logger
.debug("Found " + errors.size() + " validation errors in SAML response [" + response.getID() + "]");
}
Saml2Error first = errors.iterator().next();
throw new Saml2AuthenticationException(first);
}
else {
reportErrors(response, errors);
}
private void reportErrors(Response response, Collection<Saml2Error> errors) {
if (errors.isEmpty()) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Successfully processed SAML Response [" + response.getID() + "]");
}
return;
}
if (this.logger.isTraceEnabled()) {
this.logger.trace("Found " + errors.size() + " validation errors in SAML response [" + response.getID()
+ "]: " + errors);
}
else if (this.logger.isDebugEnabled()) {
this.logger
.debug("Found " + errors.size() + " validation errors in SAML response [" + response.getID() + "]");
}
Saml2Error first = errors.iterator().next();
throw new Saml2AuthenticationException(first);
}
private Converter<ResponseToken, Saml2ResponseValidatorResult> createDefaultResponseSignatureValidator() {
@@ -18,6 +18,7 @@ package org.springframework.security.saml2.provider.service.authentication;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
@@ -64,7 +65,7 @@ final class Saml2Utils {
static String samlInflate(byte[] b) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
InflaterOutputStream iout = new InflaterOutputStream(new CappedOutputStream(out), new Inflater(true));
iout.write(b);
iout.finish();
return new String(out.toByteArray(), StandardCharsets.UTF_8);
@@ -193,4 +194,27 @@ final class Saml2Utils {
}
static class CappedOutputStream extends OutputStream {
private static final long MAX_SIZE = 1024 * 1024;
private final OutputStream delegate;
private int size;
CappedOutputStream(OutputStream delegate) {
this.delegate = delegate;
}
@Override
public void write(int b) throws IOException {
if (this.size >= MAX_SIZE) {
throw new IOException("SAML payload exceeded maximum size of " + MAX_SIZE);
}
this.delegate.write(b);
this.size++;
}
}
}
@@ -16,8 +16,9 @@
package org.springframework.security.saml2.provider.service.authentication.logout;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.Collections;
import org.jspecify.annotations.Nullable;
import org.opensaml.saml.saml2.core.LogoutRequest;
@@ -59,86 +60,76 @@ class BaseOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValidator
LogoutRequest logoutRequest = this.saml.deserialize(Saml2Utils.withEncoded(request.getSamlRequest())
.inflate(request.getBinding() == Saml2MessageBinding.REDIRECT)
.decode());
return Saml2LogoutValidatorResult.withErrors()
.errors(verifySignature(request, logoutRequest, registration))
.errors(validateRequest(logoutRequest, registration, authentication))
.build();
Collection<Saml2Error> errors = verifySignature(request, logoutRequest, registration);
if (!errors.isEmpty()) {
return Saml2LogoutValidatorResult.withErrors(errors.toArray(Saml2Error[]::new)).build();
}
errors = validateRequest(logoutRequest, registration, authentication);
return errors.isEmpty() ? Saml2LogoutValidatorResult.success()
: Saml2LogoutValidatorResult.withErrors(errors.toArray(Saml2Error[]::new)).build();
}
private Consumer<Collection<Saml2Error>> verifySignature(Saml2LogoutRequest request, LogoutRequest logoutRequest,
private Collection<Saml2Error> verifySignature(Saml2LogoutRequest request, LogoutRequest logoutRequest,
RelyingPartyRegistration registration) {
AssertingPartyMetadata details = registration.getAssertingPartyMetadata();
Collection<Saml2X509Credential> credentials = details.getVerificationX509Credentials();
VerificationConfigurer verify = this.saml.withVerificationKeys(credentials).entityId(details.getEntityId());
return (errors) -> {
if (logoutRequest.isSigned()) {
errors.addAll(verify.verify(logoutRequest));
}
else {
String parametersQuery = request.getParametersQuery();
Assert.notNull(parametersQuery, "parametersQuery cannot be null for redirect binding");
RedirectParameters params = new RedirectParameters(request.getParameters(), parametersQuery,
logoutRequest);
errors.addAll(verify.verify(params));
}
};
if (logoutRequest.isSigned()) {
return verify.verify(logoutRequest);
}
String parametersQuery = request.getParametersQuery();
Assert.notNull(parametersQuery, "parametersQuery cannot be null for redirect binding");
RedirectParameters params = new RedirectParameters(request.getParameters(), parametersQuery, logoutRequest);
return verify.verify(params);
}
private Consumer<Collection<Saml2Error>> validateRequest(LogoutRequest request,
RelyingPartyRegistration registration, @Nullable Authentication authentication) {
return (errors) -> {
validateIssuer(request, registration).accept(errors);
validateDestination(request, registration).accept(errors);
validateSubject(request, registration, authentication).accept(errors);
};
private Collection<Saml2Error> validateRequest(LogoutRequest request, RelyingPartyRegistration registration,
@Nullable Authentication authentication) {
Collection<Saml2Error> errors = new ArrayList<>();
errors.addAll(validateIssuer(request, registration));
errors.addAll(validateDestination(request, registration));
errors.addAll(validateSubject(request, registration, authentication));
return errors;
}
private Consumer<Collection<Saml2Error>> validateIssuer(LogoutRequest request,
RelyingPartyRegistration registration) {
return (errors) -> {
if (request.getIssuer() == null) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to find issuer in LogoutRequest"));
return;
}
String issuer = request.getIssuer().getValue();
if (!registration.getAssertingPartyMetadata().getEntityId().equals(issuer)) {
errors
.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer"));
}
};
private Collection<Saml2Error> validateIssuer(LogoutRequest request, RelyingPartyRegistration registration) {
if (request.getIssuer() == null) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to find issuer in LogoutRequest"));
}
String issuer = request.getIssuer().getValue();
if (!registration.getAssertingPartyMetadata().getEntityId().equals(issuer)) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer"));
}
return Collections.emptyList();
}
private Consumer<Collection<Saml2Error>> validateDestination(LogoutRequest request,
RelyingPartyRegistration registration) {
return (errors) -> {
if (request.getDestination() == null) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to find destination in LogoutRequest"));
return;
}
String destination = request.getDestination();
if (!destination.equals(registration.getSingleLogoutServiceLocation())) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to match destination to configured destination"));
}
};
private Collection<Saml2Error> validateDestination(LogoutRequest request, RelyingPartyRegistration registration) {
if (request.getDestination() == null) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION, "Failed to find destination in LogoutRequest"));
}
String destination = request.getDestination();
if (!destination.equals(registration.getSingleLogoutServiceLocation())) {
return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to match destination to configured destination"));
}
return Collections.emptyList();
}
private Consumer<Collection<Saml2Error>> validateSubject(LogoutRequest request,
RelyingPartyRegistration registration, @Nullable Authentication authentication) {
return (errors) -> {
if (authentication == null) {
return;
}
NameID nameId = getNameId(request, registration);
if (nameId == null) {
errors
.add(new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, "Failed to find subject in LogoutRequest"));
return;
}
validateNameId(nameId, authentication, errors);
};
private Collection<Saml2Error> validateSubject(LogoutRequest request, RelyingPartyRegistration registration,
@Nullable Authentication authentication) {
if (authentication == null) {
return Collections.emptyList();
}
NameID nameId = getNameId(request, registration);
if (nameId == null) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.SUBJECT_NOT_FOUND, "Failed to find subject in LogoutRequest"));
}
return validateNameId(nameId, authentication);
}
private @Nullable NameID getNameId(LogoutRequest request, RelyingPartyRegistration registration) {
@@ -146,13 +137,14 @@ class BaseOpenSamlLogoutRequestValidator implements Saml2LogoutRequestValidator
return request.getNameID();
}
private void validateNameId(NameID nameId, Authentication authentication, Collection<Saml2Error> errors) {
private Collection<Saml2Error> validateNameId(NameID nameId, Authentication authentication) {
String name = (authentication.getCredentials() instanceof Saml2ResponseAssertionAccessor assertion)
? assertion.getNameId() : authentication.getName();
if (!name.equals(nameId.getValue())) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_REQUEST,
return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_REQUEST,
"Failed to match subject in LogoutRequest with currently logged in user"));
}
return Collections.emptyList();
}
}
@@ -16,8 +16,9 @@
package org.springframework.security.saml2.provider.service.authentication.logout;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.Collections;
import org.jspecify.annotations.Nullable;
import org.opensaml.saml.saml2.core.LogoutResponse;
@@ -54,103 +55,91 @@ class BaseOpenSamlLogoutResponseValidator implements Saml2LogoutResponseValidato
LogoutResponse logoutResponse = this.saml.deserialize(Saml2Utils.withEncoded(response.getSamlResponse())
.inflate(response.getBinding() == Saml2MessageBinding.REDIRECT)
.decode());
return Saml2LogoutValidatorResult.withErrors()
.errors(verifySignature(response, logoutResponse, registration))
.errors(validateRequest(logoutResponse, registration))
.errors(validateLogoutRequest(logoutResponse, request.getId()))
.build();
Collection<Saml2Error> errors = verifySignature(response, logoutResponse, registration);
if (!errors.isEmpty()) {
return Saml2LogoutValidatorResult.withErrors(errors.toArray(Saml2Error[]::new)).build();
}
errors = validateRequest(logoutResponse, registration, request.getId());
return errors.isEmpty() ? Saml2LogoutValidatorResult.success()
: Saml2LogoutValidatorResult.withErrors(errors.toArray(Saml2Error[]::new)).build();
}
private Consumer<Collection<Saml2Error>> verifySignature(Saml2LogoutResponse response,
LogoutResponse logoutResponse, RelyingPartyRegistration registration) {
return (errors) -> {
AssertingPartyMetadata details = registration.getAssertingPartyMetadata();
Collection<Saml2X509Credential> credentials = details.getVerificationX509Credentials();
VerificationConfigurer verify = this.saml.withVerificationKeys(credentials)
.entityId(details.getEntityId())
.entityId(details.getEntityId());
if (logoutResponse.isSigned()) {
errors.addAll(verify.verify(logoutResponse));
}
else {
String parametersQuery = response.getParametersQuery();
Assert.notNull(parametersQuery, "parametersQuery cannot be null for redirect binding");
RedirectParameters params = new RedirectParameters(response.getParameters(), parametersQuery,
logoutResponse);
errors.addAll(verify.verify(params));
}
};
}
private Consumer<Collection<Saml2Error>> validateRequest(LogoutResponse response,
private Collection<Saml2Error> verifySignature(Saml2LogoutResponse response, LogoutResponse logoutResponse,
RelyingPartyRegistration registration) {
return (errors) -> {
validateIssuer(response, registration).accept(errors);
validateDestination(response, registration).accept(errors);
validateStatus(response).accept(errors);
};
AssertingPartyMetadata details = registration.getAssertingPartyMetadata();
Collection<Saml2X509Credential> credentials = details.getVerificationX509Credentials();
VerificationConfigurer verify = this.saml.withVerificationKeys(credentials).entityId(details.getEntityId());
if (logoutResponse.isSigned()) {
return verify.verify(logoutResponse);
}
String parametersQuery = response.getParametersQuery();
Assert.notNull(parametersQuery, "parametersQuery cannot be null for redirect binding");
RedirectParameters params = new RedirectParameters(response.getParameters(), parametersQuery, logoutResponse);
return verify.verify(params);
}
private Consumer<Collection<Saml2Error>> validateIssuer(LogoutResponse response,
RelyingPartyRegistration registration) {
return (errors) -> {
if (response.getIssuer() == null) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to find issuer in LogoutResponse"));
return;
}
String issuer = response.getIssuer().getValue();
if (!registration.getAssertingPartyMetadata().getEntityId().equals(issuer)) {
errors
.add(new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer"));
}
};
private Collection<Saml2Error> validateRequest(LogoutResponse response, RelyingPartyRegistration registration,
@Nullable String logoutRequestId) {
Collection<Saml2Error> errors = new ArrayList<>();
errors.addAll(validateIssuer(response, registration));
errors.addAll(validateDestination(response, registration));
errors.addAll(validateStatus(response));
errors.addAll(validateLogoutRequest(response, logoutRequestId));
return errors;
}
private Consumer<Collection<Saml2Error>> validateDestination(LogoutResponse response,
RelyingPartyRegistration registration) {
return (errors) -> {
if (response.getDestination() == null) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to find destination in LogoutResponse"));
return;
}
String destination = response.getDestination();
if (!destination.equals(registration.getSingleLogoutServiceResponseLocation())) {
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to match destination to configured destination"));
}
};
private Collection<Saml2Error> validateIssuer(LogoutResponse response, RelyingPartyRegistration registration) {
if (response.getIssuer() == null) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to find issuer in LogoutResponse"));
}
String issuer = response.getIssuer().getValue();
if (!registration.getAssertingPartyMetadata().getEntityId().equals(issuer)) {
return Collections.singletonList(
new Saml2Error(Saml2ErrorCodes.INVALID_ISSUER, "Failed to match issuer to configured issuer"));
}
return Collections.emptyList();
}
private Consumer<Collection<Saml2Error>> validateStatus(LogoutResponse response) {
return (errors) -> {
if (response.getStatus() == null) {
return;
}
if (response.getStatus().getStatusCode() == null) {
return;
}
if (StatusCode.SUCCESS.equals(response.getStatus().getStatusCode().getValue())) {
return;
}
if (StatusCode.PARTIAL_LOGOUT.equals(response.getStatus().getStatusCode().getValue())) {
return;
}
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, "Response indicated logout failed"));
};
private Collection<Saml2Error> validateDestination(LogoutResponse response, RelyingPartyRegistration registration) {
if (response.getDestination() == null) {
return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to find destination in LogoutResponse"));
}
String destination = response.getDestination();
if (!destination.equals(registration.getSingleLogoutServiceResponseLocation())) {
return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_DESTINATION,
"Failed to match destination to configured destination"));
}
return Collections.emptyList();
}
private Consumer<Collection<Saml2Error>> validateLogoutRequest(LogoutResponse response, @Nullable String id) {
return (errors) -> {
if (response.getInResponseTo() == null) {
return;
}
if (response.getInResponseTo().equals(id)) {
return;
}
errors.add(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE,
"LogoutResponse InResponseTo doesn't match ID of associated LogoutRequest"));
};
private Collection<Saml2Error> validateStatus(LogoutResponse response) {
if (response.getStatus() == null) {
return Collections.emptyList();
}
if (response.getStatus().getStatusCode() == null) {
return Collections.emptyList();
}
if (StatusCode.SUCCESS.equals(response.getStatus().getStatusCode().getValue())) {
return Collections.emptyList();
}
if (StatusCode.PARTIAL_LOGOUT.equals(response.getStatus().getStatusCode().getValue())) {
return Collections.emptyList();
}
return Collections
.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE, "Response indicated logout failed"));
}
private Collection<Saml2Error> validateLogoutRequest(LogoutResponse response, @Nullable String id) {
if (response.getInResponseTo() == null) {
return Collections.emptyList();
}
if (response.getInResponseTo().equals(id)) {
return Collections.emptyList();
}
return Collections.singletonList(new Saml2Error(Saml2ErrorCodes.INVALID_RESPONSE,
"LogoutResponse InResponseTo doesn't match ID of associated LogoutRequest"));
}
}
@@ -18,6 +18,7 @@ package org.springframework.security.saml2.provider.service.authentication.logou
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
@@ -64,7 +65,7 @@ final class Saml2Utils {
static String samlInflate(byte[] b) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
InflaterOutputStream iout = new InflaterOutputStream(new CappedOutputStream(out), new Inflater(true));
iout.write(b);
iout.finish();
return new String(out.toByteArray(), StandardCharsets.UTF_8);
@@ -193,4 +194,27 @@ final class Saml2Utils {
}
static class CappedOutputStream extends OutputStream {
private static final long MAX_SIZE = 1024 * 1024;
private final OutputStream delegate;
private int size;
CappedOutputStream(OutputStream delegate) {
this.delegate = delegate;
}
@Override
public void write(int b) throws IOException {
if (this.size >= MAX_SIZE) {
throw new IOException("SAML payload exceeded maximum size of " + MAX_SIZE);
}
this.delegate.write(b);
this.size++;
}
}
}
@@ -18,6 +18,7 @@ package org.springframework.security.saml2.provider.service.metadata;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
@@ -64,7 +65,7 @@ final class Saml2Utils {
static String samlInflate(byte[] b) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
InflaterOutputStream iout = new InflaterOutputStream(new CappedOutputStream(out), new Inflater(true));
iout.write(b);
iout.finish();
return new String(out.toByteArray(), StandardCharsets.UTF_8);
@@ -193,4 +194,27 @@ final class Saml2Utils {
}
static class CappedOutputStream extends OutputStream {
private static final long MAX_SIZE = 1024 * 1024;
private final OutputStream delegate;
private int size;
CappedOutputStream(OutputStream delegate) {
this.delegate = delegate;
}
@Override
public void write(int b) throws IOException {
if (this.size >= MAX_SIZE) {
throw new IOException("SAML payload exceeded maximum size of " + MAX_SIZE);
}
this.delegate.write(b);
this.size++;
}
}
}
@@ -16,6 +16,11 @@
package org.springframework.security.saml2.provider.service.registration;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputFilter;
import java.io.ObjectInputStream;
import java.security.Security;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
@@ -23,13 +28,17 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.core.serializer.DefaultDeserializer;
import org.springframework.core.serializer.DefaultSerializer;
import org.springframework.core.serializer.Deserializer;
import org.springframework.core.serializer.Serializer;
@@ -39,6 +48,7 @@ import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.security.saml2.core.Saml2X509Credential;
import org.springframework.security.saml2.provider.service.registration.JdbcAssertingPartyMetadataRepository.AssertingPartyMetadataRowMapper.Saml2X509CredentialCollectionDeserializer;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration.AssertingPartyDetails;
import org.springframework.util.Assert;
import org.springframework.util.function.ThrowingFunction;
@@ -53,7 +63,7 @@ public final class JdbcAssertingPartyMetadataRepository implements AssertingPart
private final JdbcOperations jdbcOperations;
private final RowMapper<AssertingPartyMetadata> assertingPartyMetadataRowMapper = new AssertingPartyMetadataRowMapper();
private RowMapper<AssertingPartyMetadata> assertingPartyMetadataRowMapper = new AssertingPartyMetadataRowMapper();
private final AssertingPartyMetadataParametersMapper assertingPartyMetadataParametersMapper = new AssertingPartyMetadataParametersMapper();
@@ -147,13 +157,34 @@ public final class JdbcAssertingPartyMetadataRepository implements AssertingPart
return this.jdbcOperations.update(UPDATE_CREDENTIAL_RECORD_SQL, parameters.toArray());
}
/**
* Set the {@link RowMapper} to use for reading {@link AssertingPartyMetadata} records
* from the database. By default, {@link AssertingPartyMetadataRowMapper} is used.
*
* <p>
* Note that the default row mapper protects against insecure deserialization of
* credentials by way of {@link Saml2X509CredentialCollectionDeserializer}, which uses
* an allowlist of classes that can be deserialized. If a custom row mapper is used,
* it is the responsibility of the developer to ensure that any deserialization of
* credentials is done securely.
* @param rowMapper - the rowMapper to use
* @since 7.0.5
* @see AssertingPartyMetadataRowMapper
*/
public void setRowMapper(RowMapper<AssertingPartyMetadata> rowMapper) {
Assert.notNull(rowMapper, "rowMapper cannot be null");
this.assertingPartyMetadataRowMapper = rowMapper;
}
/**
* The default {@link RowMapper} that maps the current row in
* {@code java.sql.ResultSet} to {@link AssertingPartyMetadata}.
*
* @since 7.0.5
*/
private static final class AssertingPartyMetadataRowMapper implements RowMapper<AssertingPartyMetadata> {
public static final class AssertingPartyMetadataRowMapper implements RowMapper<AssertingPartyMetadata> {
private final Deserializer<Object> deserializer = new DefaultDeserializer();
private Deserializer<Collection<Saml2X509Credential>> deserializer = new Saml2X509CredentialCollectionDeserializer();
@Override
public AssertingPartyMetadata mapRow(ResultSet rs, int rowNum) throws SQLException {
@@ -165,8 +196,7 @@ public final class JdbcAssertingPartyMetadataRepository implements AssertingPart
List<String> algorithms = List.of(rs.getString(COLUMN_NAMES[4]).split(","));
byte[] verificationCredentialsBytes = rs.getBytes(COLUMN_NAMES[5]);
byte[] encryptionCredentialsBytes = rs.getBytes(COLUMN_NAMES[6]);
ThrowingFunction<byte[], Collection<Saml2X509Credential>> credentials = (
bytes) -> (Collection<Saml2X509Credential>) this.deserializer.deserializeFromByteArray(bytes);
ThrowingFunction<byte[], Collection<Saml2X509Credential>> credentials = this.deserializer::deserializeFromByteArray;
AssertingPartyMetadata.Builder<?> builder = new AssertingPartyDetails.Builder();
Collection<Saml2X509Credential> verificationCredentials = credentials.apply(verificationCredentialsBytes);
Collection<Saml2X509Credential> encryptionCredentials = (encryptionCredentialsBytes != null)
@@ -189,12 +219,121 @@ public final class JdbcAssertingPartyMetadataRepository implements AssertingPart
return builder.build();
}
/**
* Set the {@link Deserializer} to use for reading the credential collection from
* the database. By default, {@link Saml2X509CredentialCollectionDeserializer} is
* used, which uses Java Object serialization.
*
* <p>
* If a custom deserializer is used, it is the responsibility of the developer to
* ensure that any deserialization of credentials is done securely.
* @param deserializer the deserializer to use
*/
public void setCredentialsDeserializer(Deserializer<Collection<Saml2X509Credential>> deserializer) {
Assert.notNull(deserializer, "deserializer cannot be null");
this.deserializer = deserializer;
}
/**
* The default deserializer for verification and encryption credentials.
*
* <p>
* This is equipped with an allowlist of classes that can be deserialized. If you
* implement your own, you are responsible for to protecte against insecure
* deserialization.
*
* @since 7.0.5
*/
public static final class Saml2X509CredentialCollectionDeserializer
implements Deserializer<Collection<Saml2X509Credential>> {
private static final AllowlistObjectInputFilter ALLOWLIST;
static {
Set<String> classes = new LinkedHashSet<>();
classes.add(Saml2X509Credential.class.getName());
classes.add(Saml2X509Credential.Saml2X509CredentialType.class.getName());
classes.add(Enum.class.getName());
classes.add("java.security.cert.Certificate$CertificateRep");
classes.add("sun.security.x509.X509CertImpl");
classes.add(LinkedHashSet.class.getName());
classes.add(HashSet.class.getName());
classes.add("java.util.Map$Entry");
if (Security.getProvider("BC") != null) {
classes.add("org.bouncycastle.jcajce.provider.asymmetric.x509.X509CertificateObject");
}
ALLOWLIST = new AllowlistObjectInputFilter(classes);
}
@Override
@SuppressWarnings("unchecked")
public Collection<Saml2X509Credential> deserialize(InputStream in) throws IOException {
ObjectInputStream oin = new ObjectInputStream(in);
oin.setObjectInputFilter(ALLOWLIST);
try {
Collection<Saml2X509Credential> credentials = (Collection<Saml2X509Credential>) oin.readObject();
for (Object credential : credentials) {
Assert.isInstanceOf(Saml2X509Credential.class, credential,
"Deserialized object is not of type Saml2X509Credential");
}
return credentials;
}
catch (ClassNotFoundException ex) {
throw new IOException("Failed to deserialize asserting party credential collection", ex);
}
}
private static final class AllowlistObjectInputFilter implements ObjectInputFilter {
private static final Log logger = LogFactory.getLog(JdbcAssertingPartyMetadataRepository.class);
private static final int MAX_DEPTH = 20;
private static final int MAX_REFS = 1000;
private static final int MAX_ARRAY = 16384;
private static final int MAX_BYTES = 1_048_576;
private final ObjectInputFilter delegate;
private AllowlistObjectInputFilter(Set<String> allowlist) {
ObjectInputFilter pattern = ObjectInputFilter.Config
.createFilter(String.join(";", allowlist) + ";!*" + ";maxdepth=" + MAX_DEPTH + ";maxrefs="
+ MAX_REFS + ";maxarray=" + MAX_ARRAY + ";maxbytes=" + MAX_BYTES);
ObjectInputFilter global = ObjectInputFilter.Config.getSerialFilter();
this.delegate = (global != null) ? ObjectInputFilter.merge(pattern, global) : pattern;
}
@Override
public Status checkInput(FilterInfo info) {
Status status = this.delegate.checkInput(info);
if (status != Status.REJECTED) {
return status;
}
Class<?> c = info.serialClass();
if (c != null) {
logger.trace("Failed to deserialize due to [" + c.getName() + "] not being in the allowlist");
}
else {
logger.trace("Failed to deserialize due to exceeding one the following limits: " + "depth=["
+ info.depth() + " < " + MAX_DEPTH + "]" + ", refs=[" + info.references() + " < "
+ MAX_REFS + "]" + ", bytes=[" + info.streamBytes() + " < " + MAX_BYTES + "]"
+ ", arrayLength=[" + info.arrayLength() + " < " + MAX_ARRAY + "]");
}
return Status.REJECTED;
}
}
}
}
private static class AssertingPartyMetadataParametersMapper
private class AssertingPartyMetadataParametersMapper
implements Function<AssertingPartyMetadata, List<SqlParameterValue>> {
private final Serializer<Object> serializer = new DefaultSerializer();
private Serializer<Object> serializer = new DefaultSerializer();
@Override
public List<SqlParameterValue> apply(AssertingPartyMetadata record) {
@@ -18,6 +18,7 @@ package org.springframework.security.saml2.provider.service.registration;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
@@ -64,7 +65,7 @@ final class Saml2Utils {
static String samlInflate(byte[] b) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
InflaterOutputStream iout = new InflaterOutputStream(new CappedOutputStream(out), new Inflater(true));
iout.write(b);
iout.finish();
return new String(out.toByteArray(), StandardCharsets.UTF_8);
@@ -193,4 +194,27 @@ final class Saml2Utils {
}
static class CappedOutputStream extends OutputStream {
private static final long MAX_SIZE = 1024 * 1024;
private final OutputStream delegate;
private int size;
CappedOutputStream(OutputStream delegate) {
this.delegate = delegate;
}
@Override
public void write(int b) throws IOException {
if (this.size >= MAX_SIZE) {
throw new IOException("SAML payload exceeded maximum size of " + MAX_SIZE);
}
this.delegate.write(b);
this.size++;
}
}
}
@@ -18,6 +18,7 @@ package org.springframework.security.saml2.provider.service.web;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
@@ -64,7 +65,7 @@ final class Saml2Utils {
static String samlInflate(byte[] b) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
InflaterOutputStream iout = new InflaterOutputStream(new CappedOutputStream(out), new Inflater(true));
iout.write(b);
iout.finish();
return new String(out.toByteArray(), StandardCharsets.UTF_8);
@@ -193,4 +194,27 @@ final class Saml2Utils {
}
static class CappedOutputStream extends OutputStream {
private static final long MAX_SIZE = 1024 * 1024;
private final OutputStream delegate;
private int size;
CappedOutputStream(OutputStream delegate) {
this.delegate = delegate;
}
@Override
public void write(int b) throws IOException {
if (this.size >= MAX_SIZE) {
throw new IOException("SAML payload exceeded maximum size of " + MAX_SIZE);
}
this.delegate.write(b);
this.size++;
}
}
}
@@ -25,17 +25,17 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.Nullable;
import org.springframework.http.MediaType;
import org.springframework.security.saml2.core.Saml2ParameterNames;
import org.springframework.security.saml2.provider.service.authentication.AbstractSaml2AuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.Saml2PostAuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.web.authentication.Saml2AuthenticationRequestResolver;
import org.springframework.security.web.FormPostRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
@@ -68,6 +68,8 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
private Saml2AuthenticationRequestRepository<AbstractSaml2AuthenticationRequest> authenticationRequestRepository = new HttpSessionSaml2AuthenticationRequestRepository();
private final RedirectStrategy formPostRedirectStrategy = new FormPostRedirectStrategy();
/**
* Construct a {@link Saml2WebSsoAuthenticationRequestFilter} with the strategy for
* resolving the {@code AuthnRequest}
@@ -133,54 +135,11 @@ public class Saml2WebSsoAuthenticationRequestFilter extends OncePerRequestFilter
private void sendPost(HttpServletRequest request, HttpServletResponse response,
Saml2PostAuthenticationRequest authenticationRequest) throws IOException {
this.authenticationRequestRepository.saveAuthenticationRequest(authenticationRequest, request, response);
String html = createSamlPostRequestFormData(authenticationRequest);
response.setContentType(MediaType.TEXT_HTML_VALUE);
response.getWriter().write(html);
}
private String createSamlPostRequestFormData(Saml2PostAuthenticationRequest authenticationRequest) {
String authenticationRequestUri = authenticationRequest.getAuthenticationRequestUri();
String relayState = authenticationRequest.getRelayState();
String samlRequest = authenticationRequest.getSamlRequest();
StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html>\n");
html.append("<html>\n").append(" <head>\n");
html.append(" <meta http-equiv=\"Content-Security-Policy\" ")
.append("content=\"script-src 'sha256-oZhLbc2kO8b8oaYLrUc7uye1MgVKMyLtPqWR4WtKF+c='\">\n");
html.append(" <meta charset=\"utf-8\" />\n");
html.append(" </head>\n");
html.append(" <body>\n");
html.append(" <noscript>\n");
html.append(" <p>\n");
html.append(" <strong>Note:</strong> Since your browser does not support JavaScript,\n");
html.append(" you must press the Continue button once to proceed.\n");
html.append(" </p>\n");
html.append(" </noscript>\n");
html.append(" \n");
html.append(" <form action=\"");
html.append(authenticationRequestUri);
html.append("\" method=\"post\">\n");
html.append(" <div>\n");
html.append(" <input type=\"hidden\" name=\"SAMLRequest\" value=\"");
html.append(HtmlUtils.htmlEscape(samlRequest));
html.append("\"/>\n");
if (StringUtils.hasText(relayState)) {
html.append(" <input type=\"hidden\" name=\"RelayState\" value=\"");
html.append(HtmlUtils.htmlEscape(relayState));
html.append("\"/>\n");
}
html.append(" </div>\n");
html.append(" <noscript>\n");
html.append(" <div>\n");
html.append(" <input type=\"submit\" value=\"Continue\"/>\n");
html.append(" </div>\n");
html.append(" </noscript>\n");
html.append(" </form>\n");
html.append(" \n");
html.append(" <script>window.onload = function() { document.forms[0].submit(); }</script>\n");
html.append(" </body>\n");
html.append("</html>");
return html.toString();
UriComponentsBuilder uriBuilder = UriComponentsBuilder
.fromUriString(authenticationRequest.getAuthenticationRequestUri());
addParameter(Saml2ParameterNames.SAML_REQUEST, authenticationRequest.getSamlRequest(), uriBuilder);
addParameter(Saml2ParameterNames.RELAY_STATE, authenticationRequest.getRelayState(), uriBuilder);
this.formPostRedirectStrategy.sendRedirect(request, response, uriBuilder.build(true).toUriString());
}
}
@@ -18,6 +18,7 @@ package org.springframework.security.saml2.provider.service.web.authentication;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
@@ -64,7 +65,7 @@ final class Saml2Utils {
static String samlInflate(byte[] b) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
InflaterOutputStream iout = new InflaterOutputStream(new CappedOutputStream(out), new Inflater(true));
iout.write(b);
iout.finish();
return new String(out.toByteArray(), StandardCharsets.UTF_8);
@@ -193,4 +194,27 @@ final class Saml2Utils {
}
static class CappedOutputStream extends OutputStream {
private static final long MAX_SIZE = 1024 * 1024;
private final OutputStream delegate;
private int size;
CappedOutputStream(OutputStream delegate) {
this.delegate = delegate;
}
@Override
public void write(int b) throws IOException {
if (this.size >= MAX_SIZE) {
throw new IOException("SAML payload exceeded maximum size of " + MAX_SIZE);
}
this.delegate.write(b);
this.size++;
}
}
}
@@ -17,6 +17,7 @@
package org.springframework.security.saml2.provider.service.web.authentication.logout;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import jakarta.servlet.FilterChain;
@@ -28,7 +29,6 @@ import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.core.log.LogMessage;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
@@ -49,6 +49,7 @@ import org.springframework.security.saml2.provider.service.web.RelyingPartyRegis
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationPlaceholderResolvers.UriResolver;
import org.springframework.security.saml2.provider.service.web.RelyingPartyRegistrationResolver;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.FormPostRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.logout.CompositeLogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
@@ -57,8 +58,8 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
/**
* A filter for handling logout requests in the form of a &lt;saml2:LogoutRequest&gt; sent
@@ -85,6 +86,8 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter {
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private final RedirectStrategy formPostRedirectStrategy = new FormPostRedirectStrategy();
public Saml2LogoutRequestFilter(Saml2LogoutRequestValidatorParametersResolver logoutRequestResolver,
Saml2LogoutRequestValidator logoutRequestValidator, Saml2LogoutResponseResolver logoutResponseResolver,
LogoutHandler... handlers) {
@@ -205,7 +208,7 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter {
doRedirect(request, response, logoutResponse);
}
else {
doPost(response, logoutResponse);
doPost(request, response, logoutResponse);
}
}
@@ -218,55 +221,21 @@ public final class Saml2LogoutRequestFilter extends OncePerRequestFilter {
this.redirectStrategy.sendRedirect(request, response, uriBuilder.build(true).toUriString());
}
private void doPost(HttpServletResponse response, Saml2LogoutResponse logoutResponse) throws IOException {
private void doPost(HttpServletRequest request, HttpServletResponse response, Saml2LogoutResponse logoutResponse)
throws IOException {
String location = logoutResponse.getResponseLocation();
String saml = logoutResponse.getSamlResponse();
String relayState = logoutResponse.getRelayState();
String html = createSamlPostRequestFormData(location, saml, relayState);
response.setContentType(MediaType.TEXT_HTML_VALUE);
response.getWriter().write(html);
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(location);
addParameter(Saml2ParameterNames.SAML_RESPONSE, logoutResponse.getSamlResponse(), uriBuilder);
addParameter(Saml2ParameterNames.RELAY_STATE, logoutResponse.getRelayState(), uriBuilder);
this.formPostRedirectStrategy.sendRedirect(request, response, uriBuilder.build(true).toUriString());
}
private String createSamlPostRequestFormData(String location, String saml, @Nullable String relayState) {
StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html>\n");
html.append("<html>\n").append(" <head>\n");
html.append(" <meta http-equiv=\"Content-Security-Policy\" ")
.append("content=\"script-src 'sha256-oZhLbc2kO8b8oaYLrUc7uye1MgVKMyLtPqWR4WtKF+c='\">\n");
html.append(" <meta charset=\"utf-8\" />\n");
html.append(" </head>\n");
html.append(" <body>\n");
html.append(" <noscript>\n");
html.append(" <p>\n");
html.append(" <strong>Note:</strong> Since your browser does not support JavaScript,\n");
html.append(" you must press the Continue button once to proceed.\n");
html.append(" </p>\n");
html.append(" </noscript>\n");
html.append(" \n");
html.append(" <form action=\"");
html.append(location);
html.append("\" method=\"post\">\n");
html.append(" <div>\n");
html.append(" <input type=\"hidden\" name=\"SAMLResponse\" value=\"");
html.append(HtmlUtils.htmlEscape(saml));
html.append("\"/>\n");
if (StringUtils.hasText(relayState)) {
html.append(" <input type=\"hidden\" name=\"RelayState\" value=\"");
html.append(HtmlUtils.htmlEscape(relayState));
html.append("\"/>\n");
private void addParameter(String name, @Nullable String value, UriComponentsBuilder builder) {
Assert.hasText(name, "name cannot be empty or null");
if (StringUtils.hasText(value)) {
builder.queryParam(UriUtils.encode(name, StandardCharsets.ISO_8859_1),
UriUtils.encode(value, StandardCharsets.ISO_8859_1));
}
html.append(" </div>\n");
html.append(" <noscript>\n");
html.append(" <div>\n");
html.append(" <input type=\"submit\" value=\"Continue\"/>\n");
html.append(" </div>\n");
html.append(" </noscript>\n");
html.append(" </form>\n");
html.append(" \n");
html.append(" <script>window.onload = function() { document.forms[0].submit(); }</script>\n");
html.append(" </body>\n");
html.append("</html>");
return html.toString();
}
private static class Saml2AssertingPartyLogoutRequestResolver
@@ -17,6 +17,7 @@
package org.springframework.security.saml2.provider.service.web.authentication.logout;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -24,17 +25,18 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.saml2.core.Saml2ParameterNames;
import org.springframework.security.saml2.provider.service.authentication.logout.Saml2LogoutRequest;
import org.springframework.security.saml2.provider.service.registration.Saml2MessageBinding;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.FormPostRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
/**
* A success handler for issuing a SAML 2.0 Logout Request to the SAML 2.0 Asserting Party
@@ -50,6 +52,8 @@ public final class Saml2RelyingPartyInitiatedLogoutSuccessHandler implements Log
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private final RedirectStrategy formPostRedirectStrategy = new FormPostRedirectStrategy();
private Saml2LogoutRequestRepository logoutRequestRepository = new HttpSessionLogoutRequestRepository();
/**
@@ -88,7 +92,7 @@ public final class Saml2RelyingPartyInitiatedLogoutSuccessHandler implements Log
doRedirect(request, response, logoutRequest);
}
else {
doPost(response, logoutRequest);
doPost(request, response, logoutRequest);
}
}
@@ -111,55 +115,21 @@ public final class Saml2RelyingPartyInitiatedLogoutSuccessHandler implements Log
this.redirectStrategy.sendRedirect(request, response, uriBuilder.build(true).toUriString());
}
private void doPost(HttpServletResponse response, Saml2LogoutRequest logoutRequest) throws IOException {
private void doPost(HttpServletRequest request, HttpServletResponse response, Saml2LogoutRequest logoutRequest)
throws IOException {
String location = logoutRequest.getLocation();
String saml = logoutRequest.getSamlRequest();
String relayState = logoutRequest.getRelayState();
String html = createSamlPostRequestFormData(location, saml, relayState);
response.setContentType(MediaType.TEXT_HTML_VALUE);
response.getWriter().write(html);
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(location);
addParameter(Saml2ParameterNames.SAML_REQUEST, logoutRequest.getSamlRequest(), uriBuilder);
addParameter(Saml2ParameterNames.RELAY_STATE, logoutRequest.getRelayState(), uriBuilder);
this.formPostRedirectStrategy.sendRedirect(request, response, uriBuilder.build(true).toUriString());
}
private String createSamlPostRequestFormData(String location, String saml, @Nullable String relayState) {
StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html>\n");
html.append("<html>\n").append(" <head>\n");
html.append(" <meta http-equiv=\"Content-Security-Policy\" ")
.append("content=\"script-src 'sha256-oZhLbc2kO8b8oaYLrUc7uye1MgVKMyLtPqWR4WtKF+c='\">\n");
html.append(" <meta charset=\"utf-8\" />\n");
html.append(" </head>\n");
html.append(" <body>\n");
html.append(" <noscript>\n");
html.append(" <p>\n");
html.append(" <strong>Note:</strong> Since your browser does not support JavaScript,\n");
html.append(" you must press the Continue button once to proceed.\n");
html.append(" </p>\n");
html.append(" </noscript>\n");
html.append(" \n");
html.append(" <form action=\"");
html.append(location);
html.append("\" method=\"post\">\n");
html.append(" <div>\n");
html.append(" <input type=\"hidden\" name=\"SAMLRequest\" value=\"");
html.append(HtmlUtils.htmlEscape(saml));
html.append("\"/>\n");
if (StringUtils.hasText(relayState)) {
html.append(" <input type=\"hidden\" name=\"RelayState\" value=\"");
html.append(HtmlUtils.htmlEscape(relayState));
html.append("\"/>\n");
private void addParameter(String name, @Nullable String value, UriComponentsBuilder builder) {
Assert.hasText(name, "name cannot be empty or null");
if (StringUtils.hasText(value)) {
builder.queryParam(UriUtils.encode(name, StandardCharsets.ISO_8859_1),
UriUtils.encode(value, StandardCharsets.ISO_8859_1));
}
html.append(" </div>\n");
html.append(" <noscript>\n");
html.append(" <div>\n");
html.append(" <input type=\"submit\" value=\"Continue\"/>\n");
html.append(" </div>\n");
html.append(" </noscript>\n");
html.append(" </form>\n");
html.append(" \n");
html.append(" <script>window.onload = function() { document.forms[0].submit(); }</script>\n");
html.append(" </body>\n");
html.append("</html>");
return html.toString();
}
}
@@ -18,6 +18,7 @@ package org.springframework.security.saml2.provider.service.web.authentication.l
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
@@ -64,7 +65,7 @@ final class Saml2Utils {
static String samlInflate(byte[] b) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InflaterOutputStream iout = new InflaterOutputStream(out, new Inflater(true));
InflaterOutputStream iout = new InflaterOutputStream(new CappedOutputStream(out), new Inflater(true));
iout.write(b);
iout.finish();
return new String(out.toByteArray(), StandardCharsets.UTF_8);
@@ -193,4 +194,27 @@ final class Saml2Utils {
}
static class CappedOutputStream extends OutputStream {
private static final long MAX_SIZE = 1024 * 1024;
private final OutputStream delegate;
private int size;
CappedOutputStream(OutputStream delegate) {
this.delegate = delegate;
}
@Override
public void write(int b) throws IOException {
if (this.size >= MAX_SIZE) {
throw new IOException("SAML payload exceeded maximum size of " + MAX_SIZE);
}
this.delegate.write(b);
this.size++;
}
}
}
@@ -33,6 +33,7 @@ import java.util.function.Consumer;
import javax.xml.namespace.QName;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.opensaml.core.xml.XMLObject;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.schema.XSDateTime;
@@ -185,6 +186,52 @@ public class OpenSaml5AuthenticationProviderTests {
.satisfies(errorOf(Saml2ErrorCodes.INVALID_SIGNATURE));
}
@Test
public void authenticateWhenResponseSignatureInvalidThenSkipsResponseAndAssertionValidation() {
Response response = response();
response.setDestination(DESTINATION + "invalid");
Assertion assertion = assertion();
response.getAssertions().add(signed(assertion));
TestOpenSamlObjects.signed(response, TestSaml2X509Credentials.relyingPartyDecryptingCredential(),
RELYING_PARTY_ENTITY_ID);
OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider();
Converter<ResponseToken, Saml2ResponseValidatorResult> responseValidator = mock(Converter.class);
Converter<OpenSaml5AuthenticationProvider.AssertionToken, Saml2ResponseValidatorResult> assertionValidator = mock(
Converter.class);
provider.setResponseValidator(responseValidator);
provider.setAssertionValidator(assertionValidator);
Saml2AuthenticationToken token = token(response, verifying(registration()));
assertThatExceptionOfType(Saml2AuthenticationException.class).isThrownBy(() -> provider.authenticate(token))
.satisfies(errorOf(Saml2ErrorCodes.INVALID_SIGNATURE));
verifyNoInteractions(responseValidator, assertionValidator);
}
@Test
public void authenticateWhenAssertionSignatureInvalidThenSkipsThatAssertionValidationOnly() {
Response response = response();
Assertion bad = assertion();
bad.setID("bad-assertion");
TestOpenSamlObjects.signed(bad, TestSaml2X509Credentials.relyingPartyDecryptingCredential(),
RELYING_PARTY_ENTITY_ID);
response.getAssertions().add(bad);
Assertion good = assertion();
good.setID("good-assertion");
response.getAssertions().add(signed(good));
OpenSaml5AuthenticationProvider provider = new OpenSaml5AuthenticationProvider();
Converter<OpenSaml5AuthenticationProvider.AssertionToken, Saml2ResponseValidatorResult> assertionValidator = mock(
Converter.class);
given(assertionValidator.convert(any(OpenSaml5AuthenticationProvider.AssertionToken.class)))
.willReturn(Saml2ResponseValidatorResult.success());
provider.setAssertionValidator(assertionValidator);
Saml2AuthenticationToken token = token(signed(response), verifying(registration()));
assertThatExceptionOfType(Saml2AuthenticationException.class).isThrownBy(() -> provider.authenticate(token))
.satisfies(errorOf(Saml2ErrorCodes.INVALID_SIGNATURE));
ArgumentCaptor<OpenSaml5AuthenticationProvider.AssertionToken> captor = ArgumentCaptor
.forClass(OpenSaml5AuthenticationProvider.AssertionToken.class);
verify(assertionValidator).convert(captor.capture());
assertThat(captor.getValue().getAssertion().getID()).isEqualTo("good-assertion");
}
@Test
public void authenticateWhenOpenSAMLValidationErrorThenThrowAuthenticationException() {
Response response = response();
@@ -513,7 +560,7 @@ public class OpenSaml5AuthenticationProviderTests {
EncryptedAssertion encryptedAssertion = TestOpenSamlObjects.encrypted(assertion(),
TestSaml2X509Credentials.assertingPartyEncryptingCredential());
response.getEncryptedAssertions().add(encryptedAssertion);
Saml2AuthenticationToken token = token(signed(response), registration()
Saml2AuthenticationToken token = token(signed(response), verifying(registration())
.decryptionX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartyPrivateCredential())));
assertThatExceptionOfType(Saml2AuthenticationException.class)
.isThrownBy(() -> this.provider.authenticate(token))
@@ -26,6 +26,7 @@ import org.opensaml.core.xml.XMLObject;
import org.opensaml.saml.saml2.core.LogoutRequest;
import org.springframework.security.core.Authentication;
import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.core.Saml2ErrorCodes;
import org.springframework.security.saml2.core.Saml2ParameterNames;
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
@@ -90,6 +91,38 @@ public class OpenSaml5LogoutRequestValidatorTests {
assertThat(result.hasErrors()).isFalse();
}
@Test
public void handleWhenSignatureVerificationFailsThenDoesNotValidateRequestFurther() {
RelyingPartyRegistration registration = registration().build();
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
sign(logoutRequest, registration);
logoutRequest.getIssuer().setValue("https://other-asserting-party.example");
logoutRequest.setDestination("https://wrong-destination.example");
logoutRequest.getNameID().setValue("someone-else");
Saml2LogoutRequest request = post(logoutRequest, registration);
Saml2LogoutRequestValidatorParameters parameters = new Saml2LogoutRequestValidatorParameters(request,
registration, authentication(registration));
Saml2LogoutValidatorResult result = this.validator.validate(parameters);
assertThat(result.hasErrors()).isTrue();
assertThat(result.getErrors()).extracting(Saml2Error::getErrorCode)
.containsOnly(Saml2ErrorCodes.INVALID_SIGNATURE);
}
@Test
public void handleWhenDestinationAndNameIdInvalidThenCollectsAllApplicableRequestErrors() {
RelyingPartyRegistration registration = registration().build();
LogoutRequest logoutRequest = TestOpenSamlObjects.assertingPartyLogoutRequest(registration);
logoutRequest.setDestination("https://wrong-destination.example");
logoutRequest.getNameID().setValue("wrong-user");
sign(logoutRequest, registration);
Saml2LogoutRequest request = post(logoutRequest, registration);
Saml2LogoutRequestValidatorParameters parameters = new Saml2LogoutRequestValidatorParameters(request,
registration, authentication(registration));
Saml2LogoutValidatorResult result = this.validator.validate(parameters);
assertThat(result.getErrors()).extracting(Saml2Error::getErrorCode)
.containsExactly(Saml2ErrorCodes.INVALID_DESTINATION, Saml2ErrorCodes.INVALID_REQUEST);
}
@Test
public void handleWhenInvalidIssuerThenInvalidSignatureError() {
RelyingPartyRegistration registration = registration().build();
@@ -24,6 +24,7 @@ import org.opensaml.core.xml.XMLObject;
import org.opensaml.saml.saml2.core.LogoutResponse;
import org.opensaml.saml.saml2.core.StatusCode;
import org.springframework.security.saml2.core.Saml2Error;
import org.springframework.security.saml2.core.Saml2ErrorCodes;
import org.springframework.security.saml2.core.Saml2ParameterNames;
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
@@ -58,7 +59,8 @@ public class OpenSaml5LogoutResponseValidatorTests {
Saml2LogoutResponse response = post(logoutResponse, registration);
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
this.manager.validate(parameters);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isFalse();
}
@Test
@@ -75,7 +77,50 @@ public class OpenSaml5LogoutResponseValidatorTests {
this.saml.withSigningKeys(registration.getSigningX509Credentials()));
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
this.manager.validate(parameters);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isFalse();
}
@Test
public void handleWhenSignatureVerificationFailsThenDoesNotValidateResponseFurther() {
RelyingPartyRegistration registration = registration().build();
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
.id("id")
.samlRequest("logout_request")
.build();
LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration);
sign(logoutResponse, registration);
logoutResponse.setDestination("https://wrong-destination.example");
logoutResponse.getStatus().getStatusCode().setValue(StatusCode.UNKNOWN_PRINCIPAL);
logoutResponse.setInResponseTo("wrong-id");
Saml2LogoutResponse response = post(logoutResponse, registration);
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isTrue();
assertThat(result.getErrors()).extracting(Saml2Error::getErrorCode)
.containsOnly(Saml2ErrorCodes.INVALID_SIGNATURE);
}
@Test
public void handleWhenDestinationStatusAndInResponseToInvalidThenCollectsAllApplicableRequestErrors() {
RelyingPartyRegistration registration = registration().build();
Saml2LogoutRequest logoutRequest = Saml2LogoutRequest.withRelyingPartyRegistration(registration)
.id("id")
.samlRequest("logout_request")
.build();
LogoutResponse logoutResponse = TestOpenSamlObjects.assertingPartyLogoutResponse(registration);
logoutResponse.setDestination("https://wrong-destination.example");
logoutResponse.getStatus().getStatusCode().setValue(StatusCode.UNKNOWN_PRINCIPAL);
logoutResponse.setInResponseTo("wrong-id");
sign(logoutResponse, registration);
Saml2LogoutResponse response = post(logoutResponse, registration);
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.getErrors()).extracting(Saml2Error::getErrorCode)
.containsExactly(Saml2ErrorCodes.INVALID_DESTINATION, Saml2ErrorCodes.INVALID_RESPONSE,
Saml2ErrorCodes.INVALID_RESPONSE);
}
@Test
@@ -151,7 +196,8 @@ public class OpenSaml5LogoutResponseValidatorTests {
.build();
Saml2LogoutResponseValidatorParameters parameters = new Saml2LogoutResponseValidatorParameters(response,
logoutRequest, registration);
this.manager.validate(parameters);
Saml2LogoutValidatorResult result = this.manager.validate(parameters);
assertThat(result.hasErrors()).isFalse();
}
private RelyingPartyRegistration.Builder registration() {
@@ -0,0 +1,40 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.saml2.internal;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test;
import org.springframework.security.saml2.Saml2Exception;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
class Saml2UtilsTests {
@Test
void testSaml2InflateWhenLargePayloadThenErrors() {
byte[] b = new byte[1024 * 1024 + 1];
for (int i = 0; i < b.length; i++) {
b[i] = 56;
}
byte[] deflated = Saml2Utils.samlDeflate(new String(b, StandardCharsets.UTF_8));
assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> Saml2Utils.samlInflate(deflated))
.withStackTraceContaining("SAML payload exceeded maximum size");
}
}
@@ -0,0 +1,142 @@
/*
* Copyright 2004-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.saml2.provider.service.registration;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.ObjectOutputStream;
import java.security.Security;
import java.util.HashMap;
import java.util.Map;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
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 static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link JdbcAssertingPartyMetadataRepository} when Bouncy Castle is the
* preferred JCA provider. Run in their own forked JVM (see the {@code bouncyCastleTest}
* Gradle task) so that the production code's allowlist is computed with BC registered and
* so this provider change does not leak into other tests.
*/
class JdbcAssertingPartyMetadataRepositoryBouncyCastleTests {
private static final String SCHEMA_SQL_RESOURCE = "org/springframework/security/saml2/saml2-asserting-party-metadata-schema.sql";
static {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
}
private EmbeddedDatabase db;
private JdbcAssertingPartyMetadataRepository repository;
private JdbcOperations jdbcOperations;
private final AssertingPartyMetadata metadata = TestRelyingPartyRegistrations.full()
.build()
.getAssertingPartyMetadata();
@BeforeEach
void setUp() {
this.db = createDb();
this.jdbcOperations = new JdbcTemplate(this.db);
this.repository = new JdbcAssertingPartyMetadataRepository(this.jdbcOperations);
}
@AfterEach
void tearDown() {
this.db.shutdown();
}
@Test
void findByEntityIdWhenEntityPresentThenReturns() {
this.repository.save(this.metadata);
AssertingPartyMetadata found = this.repository.findByEntityId(this.metadata.getEntityId());
assertAssertingPartyEquals(found, this.metadata);
}
@Test
void saveWhenExistingThenUpdates() {
this.repository.save(this.metadata);
boolean existing = this.metadata.getWantAuthnRequestsSigned();
this.repository.save(this.metadata.mutate().wantAuthnRequestsSigned(!existing).build());
boolean updated = this.repository.findByEntityId(this.metadata.getEntityId()).getWantAuthnRequestsSigned();
assertThat(existing).isNotEqualTo(updated);
}
@Test
void findByEntityIdWhenSerializedTypeNotInAllowlistThenFailsDeserialization() throws Exception {
this.repository.save(this.metadata);
byte[] notAllowed = serialize(new HashMap<>(Map.of("not", "allowed")));
this.jdbcOperations.update(
"UPDATE saml2_asserting_party_metadata SET verification_credentials = ? WHERE entity_id = ?",
notAllowed, this.metadata.getEntityId());
assertThatExceptionOfType(RuntimeException.class)
.isThrownBy(() -> this.repository.findByEntityId(this.metadata.getEntityId()))
.withRootCauseInstanceOf(InvalidClassException.class);
}
private static byte[] serialize(Object value) throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream(bytes)) {
oos.writeObject(value);
}
return bytes.toByteArray();
}
private static EmbeddedDatabase createDb() {
// @formatter:off
return new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.HSQL)
.setScriptEncoding("UTF-8")
.addScript(SCHEMA_SQL_RESOURCE)
.build();
// @formatter:on
}
private void assertAssertingPartyEquals(AssertingPartyMetadata found, AssertingPartyMetadata expected) {
assertThat(found).isNotNull();
assertThat(found.getEntityId()).isEqualTo(expected.getEntityId());
assertThat(found.getSingleSignOnServiceLocation()).isEqualTo(expected.getSingleSignOnServiceLocation());
assertThat(found.getSingleSignOnServiceBinding()).isEqualTo(expected.getSingleSignOnServiceBinding());
assertThat(found.getWantAuthnRequestsSigned()).isEqualTo(expected.getWantAuthnRequestsSigned());
assertThat(found.getSingleLogoutServiceLocation()).isEqualTo(expected.getSingleLogoutServiceLocation());
assertThat(found.getSingleLogoutServiceResponseLocation())
.isEqualTo(expected.getSingleLogoutServiceResponseLocation());
assertThat(found.getSingleLogoutServiceBinding()).isEqualTo(expected.getSingleLogoutServiceBinding());
assertThat(found.getSigningAlgorithms()).containsAll(expected.getSigningAlgorithms());
assertThat(found.getVerificationX509Credentials()).containsAll(expected.getVerificationX509Credentials());
assertThat(found.getEncryptionX509Credentials()).containsAll(expected.getEncryptionX509Credentials());
}
}
@@ -16,7 +16,13 @@
package org.springframework.security.saml2.provider.service.registration;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -24,12 +30,19 @@ import org.junit.jupiter.api.Test;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
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.saml2.provider.service.registration.JdbcAssertingPartyMetadataRepository.AssertingPartyMetadataRowMapper;
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.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link JdbcAssertingPartyMetadataRepository}
@@ -115,6 +128,36 @@ class JdbcAssertingPartyMetadataRepositoryTests {
assertThat(existing).isNotEqualTo(updated);
}
@Test
void saveWhenCustomRowMapperThenUses() throws Exception {
RowMapper<AssertingPartyMetadata> rowMapper = spy(new AssertingPartyMetadataRowMapper());
this.repository.setRowMapper(rowMapper);
this.repository.save(this.metadata);
this.repository.findByEntityId(this.metadata.getEntityId());
verify(rowMapper).mapRow(any(), eq(0));
}
@Test
void findByEntityIdWhenSerializedTypeNotInAllowlistThenFailsDeserialization() throws Exception {
this.repository.save(this.metadata);
byte[] notAllowed = serialize(new HashMap<>(Map.of("not", "allowed")));
this.jdbcOperations.update(
"UPDATE saml2_asserting_party_metadata SET verification_credentials = ? WHERE entity_id = ?",
notAllowed, this.metadata.getEntityId());
assertThatExceptionOfType(RuntimeException.class)
.isThrownBy(() -> this.repository.findByEntityId(this.metadata.getEntityId()))
.withRootCauseInstanceOf(InvalidClassException.class);
}
private static byte[] serialize(Object value) throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream(bytes)) {
oos.writeObject(value);
}
return bytes.toByteArray();
}
private static EmbeddedDatabase createDb() {
return createDb(SCHEMA_SQL_RESOURCE);
}
@@ -178,12 +178,10 @@ public class Saml2WebSsoAuthenticationRequestFilterTests {
given(this.authenticationRequestResolver.resolve(any())).willReturn(request);
this.filter.doFilterInternal(this.request, this.response, this.filterChain);
assertThat(this.response.getHeader("Location")).isNull();
assertThat(this.response.getContentAsString()).contains(
"<meta http-equiv=\"Content-Security-Policy\" content=\"script-src 'sha256-oZhLbc2kO8b8oaYLrUc7uye1MgVKMyLtPqWR4WtKF+c='\">")
.contains("<script>window.onload = function() { document.forms[0].submit(); }</script>")
.contains("<form action=\"https://sso-url.example.com/IDP/SSO\" method=\"post\">")
.contains("<input type=\"hidden\" name=\"SAMLRequest\"")
assertThat(this.response.getContentAsString()).contains("action=\"https://sso-url.example.com/IDP/SSO\"")
.contains("name=\"SAMLRequest\"")
.contains("value=\"" + relayStateEncoded + "\"");
assertThat(this.response.getHeader("Content-Security-Policy")).matches("script-src 'nonce-.+'");
}
@Test
@@ -118,7 +118,7 @@ public class Saml2LogoutRequestFilterTests {
verify(this.logoutRequestValidator).validate(any());
verify(this.logoutHandler).logout(any(), any(), any());
verify(this.logoutResponseResolver).resolve(any(), any());
checkResponse(response.getContentAsString(), registration);
checkResponse(response, registration);
verify(this.securityContextHolderStrategy).getContext();
}
@@ -165,7 +165,7 @@ public class Saml2LogoutRequestFilterTests {
this.logoutRequestProcessingFilter.doFilter(request, response, new MockFilterChain());
checkResponse(response.getContentAsString(), registration);
checkResponse(response, registration);
verify(this.logoutRequestValidator).validate(any());
verify(this.logoutResponseResolver).resolve(any(), any(),
argThat((ex) -> ex.getSaml2Error().getErrorCode().equals(Saml2ErrorCodes.INVALID_REQUEST)));
@@ -194,7 +194,7 @@ public class Saml2LogoutRequestFilterTests {
this.logoutRequestProcessingFilter.doFilterInternal(request, response, new MockFilterChain());
checkResponse(response.getContentAsString(), registration);
checkResponse(response, registration);
verify(this.logoutResponseResolver).resolve(any(), any(),
argThat((ex) -> ex.getSaml2Error().getErrorCode().equals(Saml2ErrorCodes.INVALID_DESTINATION)));
verifyNoInteractions(this.logoutHandler);
@@ -225,7 +225,7 @@ public class Saml2LogoutRequestFilterTests {
this.logoutRequestProcessingFilter.doFilterInternal(request, response, new MockFilterChain());
checkResponse(response.getContentAsString(), registration);
checkResponse(response, registration);
verify(this.logoutResponseResolver).resolve(any(), any(),
argThat((ex) -> ex.getSaml2Error().getErrorCode().equals(Saml2ErrorCodes.INVALID_REQUEST)));
verifyNoInteractions(this.logoutHandler);
@@ -259,15 +259,14 @@ public class Saml2LogoutRequestFilterTests {
verifyNoInteractions(this.logoutHandler);
}
private void checkResponse(String responseContent, RelyingPartyRegistration registration) {
private void checkResponse(MockHttpServletResponse response, RelyingPartyRegistration registration)
throws Exception {
String responseContent = response.getContentAsString();
assertThat(responseContent).contains(Saml2ParameterNames.SAML_RESPONSE);
assertThat(responseContent)
.contains(registration.getAssertingPartyMetadata().getSingleLogoutServiceResponseLocation());
assertThat(responseContent).contains(
"<meta http-equiv=\"Content-Security-Policy\" content=\"script-src 'sha256-oZhLbc2kO8b8oaYLrUc7uye1MgVKMyLtPqWR4WtKF+c='\">");
assertThat(responseContent)
.contains("<script>window.onload = function() { document.forms[0].submit(); }</script>");
assertThat(response.getHeader("Content-Security-Policy")).matches("script-src 'nonce-.+'");
assertThat(responseContent).contains("document.getElementById(\"redirect-form\").submit();");
}
}
@@ -99,9 +99,8 @@ public class Saml2RelyingPartyInitiatedLogoutSuccessHandlerTests {
String content = response.getContentAsString();
assertThat(content).contains(Saml2ParameterNames.SAML_REQUEST);
assertThat(content).contains(registration.getAssertingPartyMetadata().getSingleLogoutServiceLocation());
assertThat(content).contains(
"<meta http-equiv=\"Content-Security-Policy\" content=\"script-src 'sha256-oZhLbc2kO8b8oaYLrUc7uye1MgVKMyLtPqWR4WtKF+c='\">");
assertThat(content).contains("<script>window.onload = function() { document.forms[0].submit(); }</script>");
assertThat(response.getHeader("Content-Security-Policy")).matches("script-src 'nonce-.+'");
assertThat(content).contains("document.getElementById(\"redirect-form\").submit();");
}
private Saml2Authentication authentication(RelyingPartyRegistration registration) {
@@ -17,6 +17,7 @@
package org.springframework.security.web;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.Map.Entry;
@@ -30,6 +31,7 @@ import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
import org.springframework.security.crypto.keygen.StringKeyGenerator;
import org.springframework.web.util.HtmlUtils;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
/**
* Redirect using an auto-submitting HTML form using the POST method. All query params
@@ -83,8 +85,12 @@ public final class FormPostRedirectStrategy implements RedirectStrategy {
final StringBuilder hiddenInputsHtmlBuilder = new StringBuilder();
for (final Entry<String, List<String>> entry : uriComponentsBuilder.build().getQueryParams().entrySet()) {
final String name = entry.getKey();
for (final String value : entry.getValue()) {
final String name = UriUtils.decode(entry.getKey(), StandardCharsets.UTF_8);
for (final String raw : entry.getValue()) {
if (raw == null) {
continue;
}
final String value = UriUtils.decode(raw, StandardCharsets.UTF_8);
// @formatter:off
final String hiddenInput = HIDDEN_INPUT_TEMPLATE
.replace("{{name}}", HtmlUtils.htmlEscape(name))
@@ -17,6 +17,8 @@
package org.springframework.security.web.authentication.preauth.x509;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.naming.InvalidNameException;
@@ -72,7 +74,10 @@ public final class SubjectX500PrincipalExtractor implements X509PrincipalExtract
private List<Rdn> getDns(String subjectDn) {
try {
return new LdapName(subjectDn).getRdns();
// read most-specific first, see gh-19254
List<Rdn> rdns = new ArrayList<>(new LdapName(subjectDn).getRdns());
Collections.reverse(rdns);
return rdns;
}
catch (InvalidNameException ex) {
throw new BadCredentialsException("Failed to parse client certificate", ex);
@@ -16,6 +16,7 @@
package org.springframework.security.web.savedrequest;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import java.util.Objects;
@@ -63,7 +64,7 @@ public class CookieRequestCache implements RequestCache {
this.logger.debug("Request not saved as configured RequestMatcher did not match");
return;
}
String redirectUrl = UrlUtils.buildFullRequestUrl(request);
String redirectUrl = buildRelativeRequestUrl(request);
Cookie savedCookie = new Cookie(COOKIE_NAME, encodeCookie(redirectUrl));
savedCookie.setMaxAge(COOKIE_MAX_AGE);
savedCookie.setSecure(request.isSecure());
@@ -84,10 +85,14 @@ public class CookieRequestCache implements RequestCache {
return null;
}
UriComponents uriComponents = UriComponentsBuilder.fromUriString(originalURI).build();
if (!isRelativePath(originalURI)) {
this.logger.debug("Did not use saved request since cookie did not contain a relative path");
return null;
}
DefaultSavedRequest.Builder builder = new DefaultSavedRequest.Builder();
int port = getPort(uriComponents);
return builder.setScheme(uriComponents.getScheme())
.setServerName(uriComponents.getHost())
int port = request.getServerPort();
return builder.setScheme(request.getScheme())
.setServerName(request.getServerName())
.setRequestURI(uriComponents.getPath())
.setQueryString(uriComponents.getQuery())
.setServerPort(port)
@@ -97,15 +102,8 @@ public class CookieRequestCache implements RequestCache {
.build();
}
private int getPort(UriComponents uriComponents) {
int port = uriComponents.getPort();
if (port != -1) {
return port;
}
if ("https".equalsIgnoreCase(uriComponents.getScheme())) {
return 443;
}
return 80;
private boolean isRelativePath(String uri) {
return uri.startsWith("/") && !uri.startsWith("//");
}
@Override
@@ -130,13 +128,19 @@ public class CookieRequestCache implements RequestCache {
response.addCookie(removeSavedRequestCookie);
}
private static String buildRelativeRequestUrl(HttpServletRequest request) {
String uri = request.getRequestURI();
String query = request.getQueryString();
return (query != null) ? uri + "?" + query : uri;
}
private static String encodeCookie(String cookieValue) {
return Base64.getEncoder().encodeToString(cookieValue.getBytes());
return Base64.getEncoder().encodeToString(cookieValue.getBytes(StandardCharsets.UTF_8));
}
private @Nullable String decodeCookie(String encodedCookieValue) {
try {
return new String(Base64.getDecoder().decode(encodedCookieValue.getBytes()));
return new String(Base64.getDecoder().decode(encodedCookieValue.getBytes(StandardCharsets.UTF_8)));
}
catch (IllegalArgumentException ex) {
this.logger.debug("Failed decode cookie value " + encodedCookieValue);
@@ -17,6 +17,7 @@
package org.springframework.security.web.server.savedrequest;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Base64;
import java.util.Collections;
@@ -97,8 +98,13 @@ public class CookieServerRequestCache implements ServerRequestCache {
return Mono.justOrEmpty(cookieMap.getFirst(REDIRECT_URI_COOKIE_NAME))
.map(HttpCookie::getValue)
.map(CookieServerRequestCache::decodeCookie)
.onErrorResume(IllegalArgumentException.class, (ex) -> Mono.empty())
.map(URI::create);
.flatMap((decoded) -> isRelativePath(decoded) ? Mono.just(decoded) : Mono.empty())
.map(URI::create)
.onErrorResume(IllegalArgumentException.class, (ex) -> Mono.empty());
}
private boolean isRelativePath(String uri) {
return uri.startsWith("/") && !uri.startsWith("//");
}
@Override
@@ -143,11 +149,13 @@ public class CookieServerRequestCache implements ServerRequestCache {
}
private static String encodeCookie(String cookieValue) {
return new String(Base64.getEncoder().encode(cookieValue.getBytes()));
return new String(Base64.getEncoder().encode(cookieValue.getBytes(StandardCharsets.UTF_8)),
StandardCharsets.UTF_8);
}
private static String decodeCookie(String encodedCookieValue) {
return new String(Base64.getDecoder().decode(encodedCookieValue.getBytes()));
return new String(Base64.getDecoder().decode(encodedCookieValue.getBytes(StandardCharsets.UTF_8)),
StandardCharsets.UTF_8);
}
private static ServerWebExchangeMatcher createDefaultRequestMatcher() {
@@ -101,6 +101,19 @@ public class FormPostRedirectStrategyTests {
assertThat(this.response).satisfies(hasScriptSrcNonce());
}
// gh-19136
@Test
public void absoluteUrlWithPercentEncodedQueryParamsRedirect() throws IOException {
this.redirectStrategy.sendRedirect(this.request, this.response, "https://example.com/cb?payload=a%2Bb%2Fc%3D");
assertThat(this.response.getStatus()).isEqualTo(HttpStatus.OK.value());
assertThat(this.response.getContentType()).isEqualTo(MediaType.TEXT_HTML_VALUE);
assertThat(this.response.getContentAsString()).contains("action=\"https://example.com/cb\"");
assertThat(this.response.getContentAsString())
.contains("<input name=\"payload\" type=\"hidden\" value=\"a+b/c=\" />");
assertThat(this.response).satisfies(hasScriptSrcNonce());
}
private ThrowingConsumer<MockHttpServletResponse> hasScriptSrcNonce() {
return (response) -> {
final String policyDirective = response.getHeader("Content-Security-Policy");
@@ -74,6 +74,13 @@ public class SubjectDnX509PrincipalExtractorTests {
assertThat(principal).isEqualTo("Duke");
}
// gh-19254
@Test
public void defaultCNPatternReturnsMostSpecificPrincipalWhenMultipleCns() throws Exception {
Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertificateWithMultipleCns());
assertThat(principal).isEqualTo("alice");
}
@Test
public void setMessageSourceWhenNullThenThrowsException() {
assertThatIllegalArgumentException().isThrownBy(() -> this.extractor.setMessageSource(null));
@@ -60,6 +60,14 @@ public class SubjectX500PrincipalExtractorTests {
assertThat(principal).isEqualTo("luke");
}
// gh-19254
@Test
void extractWhenMultipleCnsThenExtractsMostSpecificCn() throws Exception {
Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertificateWithMultipleCns());
assertThat(principal).isEqualTo("alice");
}
@Test
void extractWhenEmailDnEmbeddedInCnThenExtractsEmail() throws Exception {
this.extractor.setExtractPrincipalNameFromEmail(true);
@@ -185,4 +185,38 @@ public final class X509TestUtils {
return (X509Certificate) cf.generateCertificate(in);
}
/**
* Builds an X.509 certificate whose subject contains more than one CN. The subject DN
* is:
*
* <pre>
* CN=alice, CN=bob, O=Example Corp, C=US
* </pre>
*
* where {@code CN=alice} is the most specific (left-most) RDN.
*/
public static X509Certificate buildTestCertificateWithMultipleCns() throws Exception {
String cert = "-----BEGIN CERTIFICATE-----\n"
+ "MIIDJzCCAg+gAwIBAgIIclOz1VulWC8wDQYJKoZIhvcNAQEMBQAwQjELMAkGA1UE\n"
+ "BhMCVVMxFTATBgNVBAoTDEV4YW1wbGUgQ29ycDEMMAoGA1UEAxMDYm9iMQ4wDAYD\n"
+ "VQQDEwVhbGljZTAeFw0yNjA2MDExOTQzMTVaFw0zNjA1MjkxOTQzMTVaMEIxCzAJ\n"
+ "BgNVBAYTAlVTMRUwEwYDVQQKEwxFeGFtcGxlIENvcnAxDDAKBgNVBAMTA2JvYjEO\n"
+ "MAwGA1UEAxMFYWxpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCX\n"
+ "q8hZrTRHJEN7+D6yK65OKeCTVU+WccI6awz6g4T6O3aoC+1IiUljsEYn+xuDfx5L\n"
+ "L/O1kejjmbYt+vzRmiILoJ9xKfW3ERcB4+gEar959Dkj6wmpsgOKRjmOvcOFOkEe\n"
+ "gU1F7t04JHOou3DaAkHMNMQV+3jsWSh9Rry7XZBDkcT8XbbagoCgSIIef03Qtw31\n"
+ "zOBwqmJmd6CQhFJva5cl8cCE2xZinOIiz/2j7VprZTjhkud2M4bRnK3T0WExyOCg\n"
+ "jvjSf5ZoOKwC2Z9Q/Oyf8WKpren1+GfZhAKKmn7QZ2foHhdPPRtNjRGE6SqQyW2u\n"
+ "8+1tOXl+aRCF19rR7gIpAgMBAAGjITAfMB0GA1UdDgQWBBRfLtnK5WKU0q3zxIrr\n"
+ "y3GD+lYplzANBgkqhkiG9w0BAQwFAAOCAQEAe+/FHqErVPsF/sHrVHny8mIsn3ux\n"
+ "qE9P24KNF0oIfmBrAqqge6hoVQ8PS+JialyqFf//osuDjiuYaBEKBw7GCoA6I8mr\n"
+ "FA7wyFaGosfq7An5vxkJl7lap2u5oSVv3dCy13Bs0ziYmNlTkfHDLy9yh7jpH1wg\n"
+ "TvylH9O4Vc7y9rzzpIjMCuQJ/wJ4MuJ2mSarYZsx3UQHIRfpKtR/9jbFMX1Rbv/A\n"
+ "N0XD+NrtFjiikp71y3aCO1EHnGG7qPKCWh3PzaNoWFpyZKDBvud8ymW7RMiLPgv9\n"
+ "eTa82KGgnCwtKCNzGkszIn7fza/6xnCuzvh4y9tYE5BWP2mcMRtRmDD07w==\n" + "-----END CERTIFICATE-----";
ByteArrayInputStream in = new ByteArrayInputStream(cert.getBytes());
CertificateFactory cf = CertificateFactory.getInstance("X.509");
return (X509Certificate) cf.generateCertificate(in);
}
}
@@ -54,7 +54,7 @@ public class CookieRequestCacheTests {
Cookie savedCookie = response.getCookie(DEFAULT_COOKIE_NAME);
assertThat(savedCookie).isNotNull();
String redirectUrl = decodeCookie(savedCookie.getValue());
assertThat(redirectUrl).isEqualTo("https://abc.com/destination?param1=a&param2=b&param3=1122");
assertThat(redirectUrl).isEqualTo("/destination?param1=a&param2=b&param3=1122");
assertThat(savedCookie.getMaxAge()).isEqualTo(-1);
assertThat(savedCookie.getPath()).isEqualTo("/");
assertThat(savedCookie.isHttpOnly()).isTrue();
@@ -101,18 +101,53 @@ public class CookieRequestCacheTests {
public void getRequestWhenRequestContainsSavedRequestCookieThenReturnsSaveRequest() {
CookieRequestCache cookieRequestCache = new CookieRequestCache();
MockHttpServletRequest request = new MockHttpServletRequest();
String redirectUrl = "https://abc.com/destination?param1=a&param2=b&param3=1122";
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie(redirectUrl)));
request.setScheme("https");
request.setServerName("abc.com");
request.setServerPort(443);
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie("/destination?param1=a&param2=b&param3=1122")));
SavedRequest savedRequest = cookieRequestCache.getRequest(request, new MockHttpServletResponse());
assertThat(savedRequest).isNotNull();
assertThat(savedRequest.getRedirectUrl()).isEqualTo(redirectUrl);
assertThat(savedRequest.getRedirectUrl())
.isEqualTo("https://abc.com/destination?param1=a&param2=b&param3=1122");
}
@Test
public void getRequestWhenRelativePathInCookieThenRedirectUrlUsesCurrentRequestOrigin() {
CookieRequestCache cookieRequestCache = new CookieRequestCache();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setScheme("https");
request.setServerName("myapp.com");
request.setServerPort(443);
request.setSecure(true);
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie("/secret?token=abc")));
SavedRequest savedRequest = cookieRequestCache.getRequest(request, new MockHttpServletResponse());
assertThat(savedRequest).isNotNull();
assertThat(savedRequest.getRedirectUrl()).isEqualTo("https://myapp.com/secret?token=abc");
}
@Test
public void getRequestWhenAbsoluteUrlInCookieThenReturnsNull() {
CookieRequestCache cookieRequestCache = new CookieRequestCache();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie("https://evil.com/phishing")));
SavedRequest savedRequest = cookieRequestCache.getRequest(request, new MockHttpServletResponse());
assertThat(savedRequest).isNull();
}
@Test
public void getRequestWhenProtocolRelativeUrlInCookieThenReturnsNull() {
CookieRequestCache cookieRequestCache = new CookieRequestCache();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie("//evil.com/phishing")));
SavedRequest savedRequest = cookieRequestCache.getRequest(request, new MockHttpServletResponse());
assertThat(savedRequest).isNull();
}
@Test
public void getRequestWhenRequestContainsSavedRequestCookieThenSavedRequestContainsRequestParameters() {
CookieRequestCache cookieRequestCache = new CookieRequestCache();
MockHttpServletRequest request = new MockHttpServletRequest();
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie("https://abc.com/destination")));
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie("/destination")));
request.setParameter("single", "first");
request.addParameter("multi", "second");
request.addParameter("multi", "third");
@@ -143,8 +178,7 @@ public class CookieRequestCacheTests {
request.setServerName("abc.com");
request.setRequestURI("/destination");
request.setQueryString("param1=a&param2=b&param3=1122");
String redirectUrl = "https://abc.com/destination?param1=a&param2=b&param3=1122";
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie(redirectUrl)));
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie("/destination?param1=a&param2=b&param3=1122")));
MockHttpServletResponse response = new MockHttpServletResponse();
cookieRequestCache.getMatchingRequest(request, response);
Cookie expiredCookie = response.getCookie(DEFAULT_COOKIE_NAME);
@@ -162,8 +196,7 @@ public class CookieRequestCacheTests {
request.setScheme("https");
request.setServerName("abc.com");
request.setRequestURI("/destination");
String redirectUrl = "https://abc.com/api";
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie(redirectUrl)));
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie("/api")));
MockHttpServletResponse response = new MockHttpServletResponse();
final HttpServletRequest matchingRequest = cookieRequestCache.getMatchingRequest(request, response);
assertThat(matchingRequest).isNull();
@@ -182,8 +215,8 @@ public class CookieRequestCacheTests {
request.setRequestURI("/destination");
request.setQueryString("goto=https%3A%2F%2Fstart.spring.io");
request.setParameter("goto", "https://start.spring.io");
String redirectUrl = "https://abc.com/destination?goto=https%3A%2F%2Fstart.spring.io";
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie(redirectUrl)));
request.setCookies(
new Cookie(DEFAULT_COOKIE_NAME, encodeCookie("/destination?goto=https%3A%2F%2Fstart.spring.io")));
MockHttpServletResponse response = new MockHttpServletResponse();
final HttpServletRequest matchingRequest = cookieRequestCache.getMatchingRequest(request, response);
assertThat(matchingRequest).isNotNull();
@@ -212,7 +245,7 @@ public class CookieRequestCacheTests {
request.setServerName("example.com");
request.setRequestURI("/destination");
request.setPreferredLocales(Arrays.asList(Locale.FRENCH, Locale.GERMANY));
String redirectUrl = "https://example.com/destination";
String redirectUrl = "/destination";
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie(redirectUrl)));
MockHttpServletResponse response = new MockHttpServletResponse();
HttpServletRequest matchingRequest = cookieRequestCache.getMatchingRequest(request, response);
@@ -16,8 +16,6 @@
package org.springframework.security.web.savedrequest;
import java.util.Base64;
import jakarta.servlet.http.Cookie;
import org.junit.jupiter.api.Test;
@@ -53,14 +51,15 @@ public class RequestCacheAwareFilterTests {
request.setScheme("https");
request.setServerPort(443);
request.setSecure(true);
String encodedRedirectUrl = Base64.getEncoder().encodeToString("https://abc.com/destination".getBytes());
Cookie savedRequest = new Cookie("REDIRECT_URI", encodedRedirectUrl);
MockHttpServletResponse response = new MockHttpServletResponse();
cache.saveRequest(request, response);
Cookie savedRequest = response.getCookie("REDIRECT_URI");
savedRequest.setMaxAge(-1);
savedRequest.setSecure(request.isSecure());
savedRequest.setPath("/");
savedRequest.setHttpOnly(true);
request.setCookies(savedRequest);
MockHttpServletResponse response = new MockHttpServletResponse();
response = new MockHttpServletResponse();
filter.doFilter(request, response, new MockFilterChain());
Cookie expiredCookie = response.getCookie("REDIRECT_URI");
assertThat(expiredCookie).isNotNull();
@@ -117,6 +117,26 @@ public class CookieServerRequestCacheTests {
assertThat(redirectUri).isNull();
}
@Test
public void getRedirectUriWhenCookieContainsAbsoluteUrlThenRedirectUriIsNull() {
String encodedAbsoluteUrl = Base64.getEncoder().encodeToString("https://evil.com/phishing".getBytes());
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/login")
.accept(MediaType.TEXT_HTML)
.cookie(new HttpCookie("REDIRECT_URI", encodedAbsoluteUrl)));
URI redirectUri = this.cache.getRedirectUri(exchange).block();
assertThat(redirectUri).isNull();
}
@Test
public void getRedirectUriWhenCookieContainsProtocolRelativeUrlThenRedirectUriIsNull() {
String encodedProtocolRelativeUrl = Base64.getEncoder().encodeToString("//evil.com/phishing".getBytes());
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/login")
.accept(MediaType.TEXT_HTML)
.cookie(new HttpCookie("REDIRECT_URI", encodedProtocolRelativeUrl)));
URI redirectUri = this.cache.getRedirectUri(exchange).block();
assertThat(redirectUri).isNull();
}
@Test
public void getRedirectUriWhenNoCookieThenRedirectUriIsNull() {
MockServerWebExchange exchange = MockServerWebExchange