1
0
mirror of synced 2026-07-12 06:10:02 +00:00

Compare commits

..

108 Commits

Author SHA1 Message Date
github-actions[bot] c8a0601e6a Release 5.8.11 2024-03-18 11:10:03 +00:00
Marcus Hert Da Coregio 2c9dc08e43 Merge branch '5.7.x' into 5.8.x
Closes gh-14664
2024-03-18 06:40:34 -03:00
Marcus Hert Da Coregio 5a7f12f1a9 Check for null Authentication
Closes gh-14715
2024-03-18 06:39:08 -03:00
dependabot[bot] 3b355a0278 Bump org.springframework:spring-framework-bom from 5.3.32 to 5.3.33
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 5.3.32 to 5.3.33.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.32...v5.3.33)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-14 21:07:46 -07:00
Marcus Hert Da Coregio a6d362fa18 Make trigger-dependabot-auto-merge-forward.yml run on push
Issue gh-14721
2024-03-14 15:12:14 -03:00
Marcus Hert Da Coregio 37984c2e11 Add GH_TOKEN to set-milestone
Issue gh-14721
2024-03-14 14:45:36 -03:00
Marcus Hert Da Coregio b3a43d6154 Use token only for auto merge
Issue gh-14721
2024-03-14 14:42:33 -03:00
Marcus Hert Da Coregio a87fc4ea8a Use pull_request_target for merge-dependabot-pr.yml
Issue gh-14721
2024-03-14 14:33:00 -03:00
Marcus Hert Da Coregio e7bff4240a Use GH_ACTIONS_REPO_TOKEN for merge-dependabot-pr.yml
Issue gh-14721
2024-03-14 09:23:31 -03:00
dependabot[bot] 4e6b8e4d29 Bump io.projectreactor:reactor-bom from 2020.0.41 to 2020.0.42
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.41 to 2020.0.42.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2020.0.41...2020.0.42)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-13 04:24:14 +00:00
dependabot[bot] e550c48180 Bump io.projectreactor.netty:reactor-netty from 1.0.41 to 1.0.43
Bumps [io.projectreactor.netty:reactor-netty](https://github.com/reactor/reactor-netty) from 1.0.41 to 1.0.43.
- [Release notes](https://github.com/reactor/reactor-netty/releases)
- [Commits](https://github.com/reactor/reactor-netty/compare/v1.0.41...v1.0.43)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-13 04:11:01 +00:00
Marcus Hert Da Coregio a3c06d98ca Trigger Dependabot Workflow when PR is merged
Issue gh-14721
2024-03-12 10:26:17 -03:00
Marcus Hert Da Coregio 8f42c86a57 Use AuthorizationInterceptorsOrder for Post Authorize Method Interceptors
Closes gh-14720
2024-03-12 10:17:45 -03:00
Marcus Hert Da Coregio 5dae6da15a Remove branch filter
Issue gh-14535
2024-03-11 09:25:58 -03:00
Josh Cummings e1c4177cd8 Fix ServerLogoutHandler Order in Docs
Closes gh-14379
2024-03-04 15:42:09 -07:00
Marcus Hert Da Coregio c2e97d7661 Ignore dependabot branches on trigger auto merge 2024-02-29 10:52:44 -03:00
Josh Cummings 94a66f7a26 Change Structure101 Repository URL
Issug gh-14639
2024-02-26 10:03:09 -07:00
Christian Becker 5f80468de3 Updated copyright date 2024-02-22 13:19:05 -07:00
Christian Becker 2f762fefe1 Allow tab in HTTP header values.
Closes gh-14573
2024-02-22 13:19:05 -07:00
DingHao 45c37c4454 Remove duplicate setSecurityContextHolderStrategy
Closes gh-14592
2024-02-22 12:14:35 -07:00
Josh Cummings 0c70f358d5 Update to Structure101 7.0.24375
Closes gh-14629
2024-02-20 15:43:58 -07:00
github-actions[bot] 7d38686e09 Next development version 2024-02-16 19:29:53 +00:00
github-actions[bot] 8b9beb0e1f Release 5.8.10 2024-02-16 18:47:27 +00:00
Marcus Hert Da Coregio 6230806d56 Change branch pattern
Issue gh-14535
2024-02-16 11:51:53 -03:00
Marcus Hert Da Coregio 2159f3a93d Fix branch pattern
Issue gh-14535
2024-02-16 09:50:42 -03:00
dependabot[bot] dc366cf8da Bump org.springframework:spring-framework-bom from 5.3.31 to 5.3.32
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 5.3.31 to 5.3.32.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.31...v5.3.32)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-16 04:04:11 +00:00
Marcus Hert Da Coregio d32202bce3 Revert "Bump org-aspectj from 1.9.20.1 to 1.9.21.1"
This reverts commit a0b1d6317c.
2024-02-15 09:45:49 -03:00
dependabot[bot] 349ac07c10 Bump io.projectreactor:reactor-bom from 2020.0.40 to 2020.0.41
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.40 to 2020.0.41.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2020.0.40...2020.0.41)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-15 06:42:20 -06:00
Marcus Hert Da Coregio 59be5553a1 Fix workflow syntax
Issue gh-14535
2024-02-15 09:23:35 -03:00
Marcus Hert Da Coregio 2bfdb0c6cd Only run workflow for branches that matches [5-9]+.[5-9]+.x
Issue gh-14535
2024-02-14 01:15:16 -03:00
dependabot[bot] 2ed7b857c3 Bump org-eclipse-jetty from 9.4.53.v20231009 to 9.4.54.v20240208
Bumps `org-eclipse-jetty` from 9.4.53.v20231009 to 9.4.54.v20240208.

Updates `org.eclipse.jetty:jetty-server` from 9.4.53.v20231009 to 9.4.54.v20240208

Updates `org.eclipse.jetty:jetty-servlet` from 9.4.53.v20231009 to 9.4.54.v20240208

---
updated-dependencies:
- dependency-name: org.eclipse.jetty:jetty-server
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.eclipse.jetty:jetty-servlet
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-14 03:57:50 +00:00
dependabot[bot] a0b1d6317c Bump org-aspectj from 1.9.20.1 to 1.9.21.1
Bumps `org-aspectj` from 1.9.20.1 to 1.9.21.1.

Updates `org.aspectj:aspectjrt` from 1.9.20.1 to 1.9.21.1
- [Release notes](https://github.com/eclipse/org.aspectj/releases)
- [Commits](https://github.com/eclipse/org.aspectj/commits)

Updates `org.aspectj:aspectjweaver` from 1.9.20.1 to 1.9.21.1
- [Release notes](https://github.com/eclipse/org.aspectj/releases)
- [Commits](https://github.com/eclipse/org.aspectj/commits)

---
updated-dependencies:
- dependency-name: org.aspectj:aspectjrt
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.aspectj:aspectjweaver
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-14 03:57:41 +00:00
Marcus Hert Da Coregio 731cd6131b Do not try to run trigger dependabot automerge workflow in PRs
Issue gh-14535
2024-02-13 10:45:08 -03:00
dependabot[bot] c2a589734c Bump spring-io/spring-github-workflows
Bumps [spring-io/spring-github-workflows](https://github.com/spring-io/spring-github-workflows) from eaf17a1890b1ef1b337f015d6eb263baaf8c6dab to 1e8b0587a1f4f01697f9753fa3339c3e0d30f396.
- [Release notes](https://github.com/spring-io/spring-github-workflows/releases)
- [Commits](https://github.com/spring-io/spring-github-workflows/compare/eaf17a1890b1ef1b337f015d6eb263baaf8c6dab...1e8b0587a1f4f01697f9753fa3339c3e0d30f396)

---
updated-dependencies:
- dependency-name: spring-io/spring-github-workflows
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 00:57:15 +00:00
dependabot[bot] e5e67f91e9 Bump Gamesight/slack-workflow-status from 1.2.0 to 1.3.0
Bumps [Gamesight/slack-workflow-status](https://github.com/gamesight/slack-workflow-status) from 1.2.0 to 1.3.0.
- [Release notes](https://github.com/gamesight/slack-workflow-status/releases)
- [Commits](https://github.com/gamesight/slack-workflow-status/compare/v1.2.0...v1.3.0)

---
updated-dependencies:
- dependency-name: Gamesight/slack-workflow-status
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-12 00:57:09 +00:00
Marcus Hert Da Coregio ccb2f06d0d Partially revert fc658d10
OpenIDAuthenticationFilter exists in versions < 6.0

Issue gh-14531
2024-02-07 10:13:34 -03:00
DingHao fc658d10d3 fix security filter sort in javadoc
Closes gh-14531
2024-02-07 09:37:01 -03:00
Marcus Hert Da Coregio e0fc8f37b0 Use latest version of spring-merge-dependabot-pr workflow
Issue gh-14535
2024-02-06 11:05:56 -03:00
Marcus Hert Da Coregio 43ced5291b Only run workflow for spring-projects repositories
Issue gh-14535
2024-02-06 08:18:31 -03:00
Marcus Hert Da Coregio 56c5fc281e Automatically trigger auto merge for Dependabot PRs
Issue gh-14535
2024-02-06 08:06:27 -03:00
Josh Cummings 7c3a6a567e Fix Compilation Errors
Issue gh-14525
2024-02-05 15:18:31 -07:00
Andreas Asplund 07e0b1dc37 Saml2 LogoutFilter Is Placed Before Common LogoutFilter
Closes gh-14525
2024-02-05 15:18:31 -07:00
Marcus Hert Da Coregio 05d3c4b695 Add permission to Edit Dependabot PR workflow
Issue gh-14486
2024-02-02 09:30:50 -03:00
Marcus Hert Da Coregio 5f80cfc705 Automatically assign milestone to Dependabot PR
Issue gh-14486
2024-02-02 09:30:39 -03:00
dependabot[bot] 7011930305 Bump gradle/gradle-build-action from 2 to 3
Bumps [gradle/gradle-build-action](https://github.com/gradle/gradle-build-action) from 2 to 3.
- [Release notes](https://github.com/gradle/gradle-build-action/releases)
- [Commits](https://github.com/gradle/gradle-build-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: gradle/gradle-build-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-29 17:44:51 -07:00
dependabot[bot] 8a75382b2d Bump slackapi/slack-github-action from 1.24.0 to 1.25.0
Bumps [slackapi/slack-github-action](https://github.com/slackapi/slack-github-action) from 1.24.0 to 1.25.0.
- [Release notes](https://github.com/slackapi/slack-github-action/releases)
- [Commits](https://github.com/slackapi/slack-github-action/compare/v1.24.0...v1.25.0)

---
updated-dependencies:
- dependency-name: slackapi/slack-github-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-29 17:41:43 -07:00
Hans Lindner ca10187fd1 Enhance JWT decoding error handling
Previously, the `decode` method threw a `JwtException` directly when encountering an unsupported algorithm or any exception during parsing. This commit introduces a more robust error handling mechanism. Now, instead of throwing exceptions directly, it returns a `Mono.error()` with a `BadJwtException` containing detailed error information. This approach provides more flexibility and allows the caller to handle errors in a more granular way, by being able to use project reactors onError functionality.

Closes gh-14467
2024-01-25 17:32:10 -07:00
dependabot[bot] 56f486588f Bump io.spring.ge.conventions from 0.0.14 to 0.0.15
Bumps [io.spring.ge.conventions](https://github.com/spring-io/gradle-enterprise-conventions) from 0.0.14 to 0.0.15.
- [Release notes](https://github.com/spring-io/gradle-enterprise-conventions/releases)
- [Commits](https://github.com/spring-io/gradle-enterprise-conventions/compare/v0.0.14...v0.0.15)

---
updated-dependencies:
- dependency-name: io.spring.ge.conventions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-17 14:31:01 -03:00
dependabot[bot] 3f5f79d835 Bump io.projectreactor.netty:reactor-netty from 1.0.40 to 1.0.41
Bumps [io.projectreactor.netty:reactor-netty](https://github.com/reactor/reactor-netty) from 1.0.40 to 1.0.41.
- [Release notes](https://github.com/reactor/reactor-netty/releases)
- [Commits](https://github.com/reactor/reactor-netty/compare/v1.0.40...v1.0.41)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-12 09:52:45 -06:00
dependabot[bot] 9985580534 Bump io.projectreactor:reactor-bom from 2020.0.39 to 2020.0.40
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.39 to 2020.0.40.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2020.0.39...2020.0.40)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-12 09:42:04 -06:00
dependabot[bot] 24d4abe5fd Bump io-spring-javaformat from 0.0.40 to 0.0.41
Bumps `io-spring-javaformat` from 0.0.40 to 0.0.41.

Updates `io.spring.javaformat:spring-javaformat-checkstyle` from 0.0.40 to 0.0.41
- [Release notes](https://github.com/spring-io/spring-javaformat/releases)
- [Commits](https://github.com/spring-io/spring-javaformat/compare/v0.0.40...v0.0.41)

Updates `io.spring.javaformat:spring-javaformat-gradle-plugin` from 0.0.40 to 0.0.41
- [Release notes](https://github.com/spring-io/spring-javaformat/releases)
- [Commits](https://github.com/spring-io/spring-javaformat/compare/v0.0.40...v0.0.41)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-12 09:37:19 -06:00
Steve Riesenberg 16dc6be3c8 Update copyright year
Issue gh-14329
2023-12-28 12:54:29 -06:00
Geir Hedemark c88aaedb48 Updated broken documentation link in javadocs 2023-12-28 12:54:29 -06:00
daniKir 9203567a20 improve Multitenancy Issuer Validator example Closes gh-14229 2023-12-19 18:04:50 -07:00
Josh Cummings e058b559b8 Polish Method Security Eager-Loading
Issue gh-11596
2023-12-18 15:18:09 -07:00
Josh Cummings 33800c0124 Address eager-loading of infrastructure beans
Closes gh-11596
2023-12-18 14:25:48 -07:00
github-actions[bot] b71962e87d Next development version 2023-12-18 19:49:40 +00:00
github-actions[bot] b97c310aba Release 5.8.9 2023-12-18 19:12:02 +00:00
Josh Cummings 59461d94b0 Clarify RSocket Configuration Docs
Closes gh-13718
2023-12-18 12:02:49 -07:00
Josh Cummings fc007aa373 Check OpenSAML Version in XML Support
Closes gh-12483
2023-12-18 11:51:15 -07:00
Josh Cummings eaaa813ede Fix header value typo
Closes gh-11948
2023-12-18 10:42:50 -07:00
dependabot[bot] 10b3a9b234 Bump Gamesight/slack-workflow-status from 1.0.1 to 1.2.0
Bumps [Gamesight/slack-workflow-status](https://github.com/gamesight/slack-workflow-status) from 1.0.1 to 1.2.0.
- [Release notes](https://github.com/gamesight/slack-workflow-status/releases)
- [Commits](https://github.com/gamesight/slack-workflow-status/compare/v1.0.1...v1.2.0)

---
updated-dependencies:
- dependency-name: Gamesight/slack-workflow-status
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-14 08:58:50 -03:00
dependabot[bot] e4cc0a4755 Bump slackapi/slack-github-action from 1.19.0 to 1.24.0
Bumps [slackapi/slack-github-action](https://github.com/slackapi/slack-github-action) from 1.19.0 to 1.24.0.
- [Release notes](https://github.com/slackapi/slack-github-action/releases)
- [Commits](https://github.com/slackapi/slack-github-action/compare/v1.19.0...v1.24.0)

---
updated-dependencies:
- dependency-name: slackapi/slack-github-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-14 08:58:37 -03:00
dependabot[bot] ff71ef46b7 Bump actions/setup-java from 3 to 4
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 3 to 4.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-14 08:58:19 -03:00
dependabot[bot] 5593655bbf Bump spring-io/spring-gradle-build-action from 1 to 2
Bumps [spring-io/spring-gradle-build-action](https://github.com/spring-io/spring-gradle-build-action) from 1 to 2.
- [Commits](https://github.com/spring-io/spring-gradle-build-action/compare/v1...v2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-14 08:58:09 -03:00
dependabot[bot] 47812e506f Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [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/v3...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-14 08:58:00 -03:00
dependabot[bot] 89fd30f235 Bump io.projectreactor.netty:reactor-netty from 1.0.39 to 1.0.40
Bumps [io.projectreactor.netty:reactor-netty](https://github.com/reactor/reactor-netty) from 1.0.39 to 1.0.40.
- [Release notes](https://github.com/reactor/reactor-netty/releases)
- [Commits](https://github.com/reactor/reactor-netty/compare/v1.0.39...v1.0.40)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-13 09:16:42 -03:00
dependabot[bot] c3c068376a Bump io.projectreactor:reactor-bom from 2020.0.38 to 2020.0.39
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.38 to 2020.0.39.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2020.0.38...2020.0.39)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-13 09:15:35 -03:00
Marcus Hert Da Coregio 6d68f403fc Document that Shibboleth Repository is Required for SAML Support
Closes gh-14286
2023-12-13 09:11:27 -03:00
Josh Cummings 74d06f020d Update to Latest Boot Property
Closes gh-14252
2023-12-11 11:44:08 -07:00
Candelario e896b14046 Dropped Nimbus Error Message
Closes gh-13730
2023-12-11 10:19:02 -07:00
Josh Cummings be11812fe4 Account for Super-super-interface Inheritance
Closes gh-13625
2023-12-09 11:41:02 -07:00
Steve Riesenberg 9c44b700f5 Add Gradle Enterprise creds to PR builds
Closes gh-14249
2023-12-07 14:28:51 -06:00
dependabot[bot] 40e8b20257 Bump ch.qos.logback:logback-classic from 1.2.12 to 1.2.13
Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.2.12 to 1.2.13.
- [Commits](https://github.com/qos-ch/logback/compare/v_1.2.12...v_1.2.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-04 08:51:58 -07:00
Josh Cummings c336ca49fb Update Spring MVC Docs
Closes gh-14220
2023-12-01 12:57:46 -07:00
Josh Cummings c623303ca5 Add Logging
Now if the ServletRegistration API available message is shown, it will
also be accompanied with a startup warning in the logs.

Closes gh-14221
2023-12-01 12:57:46 -07:00
Josh Cummings a98baa7522 Polish ServletRegistration API Deferral
Tomcat uses different ServletContext instances from startup- and request-time.
This commit ensures that if the programmatic API isn't available at startup-time,
then use the ServletContext attached to the HttpServletRequest at runtime.

Issue gh-13794
2023-12-01 12:57:45 -07:00
dependabot[bot] bb0987db80 Bump org.springframework.data:spring-data-bom
Bumps [org.springframework.data:spring-data-bom](https://github.com/spring-projects/spring-data-bom) from 2021.2.17 to 2021.2.18.
- [Release notes](https://github.com/spring-projects/spring-data-bom/releases)
- [Commits](https://github.com/spring-projects/spring-data-bom/compare/2021.2.17...2021.2.18)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-20 08:51:36 -03:00
Josh Cummings d961307044 Polish RequestMatcher Description
Issue gh-13794
2023-11-17 12:24:38 -07:00
Josh Cummings 4ca54683ae Defer requestMatchers Validation to Runtime
Closes gh-13794
2023-11-17 11:23:21 -07:00
Marcus Hert Da Coregio a7da9491d9 Use assertj assertions 2023-11-17 09:03:36 -03:00
dependabot[bot] 382cfd63ed Bump io-spring-javaformat from 0.0.39 to 0.0.40
Bumps `io-spring-javaformat` from 0.0.39 to 0.0.40.

Updates `io.spring.javaformat:spring-javaformat-checkstyle` from 0.0.39 to 0.0.40
- [Release notes](https://github.com/spring-io/spring-javaformat/releases)
- [Commits](https://github.com/spring-io/spring-javaformat/compare/v0.0.39...v0.0.40)

Updates `io.spring.javaformat:spring-javaformat-gradle-plugin` from 0.0.39 to 0.0.40
- [Release notes](https://github.com/spring-io/spring-javaformat/releases)
- [Commits](https://github.com/spring-io/spring-javaformat/compare/v0.0.39...v0.0.40)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-17 09:03:36 -03:00
dependabot[bot] 7c98a39770 Bump org.springframework:spring-framework-bom from 5.3.30 to 5.3.31
Bumps [org.springframework:spring-framework-bom](https://github.com/spring-projects/spring-framework) from 5.3.30 to 5.3.31.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.30...v5.3.31)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-17 09:03:29 -03:00
dependabot[bot] 6afcc02ef3 Bump io.projectreactor:reactor-bom from 2020.0.37 to 2020.0.38
Bumps [io.projectreactor:reactor-bom](https://github.com/reactor/reactor) from 2020.0.37 to 2020.0.38.
- [Release notes](https://github.com/reactor/reactor/releases)
- [Commits](https://github.com/reactor/reactor/compare/2020.0.37...2020.0.38)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-16 08:35:55 -03:00
dependabot[bot] 773673ac2c Bump io.projectreactor.netty:reactor-netty from 1.0.38 to 1.0.39
Bumps [io.projectreactor.netty:reactor-netty](https://github.com/reactor/reactor-netty) from 1.0.38 to 1.0.39.
- [Release notes](https://github.com/reactor/reactor-netty/releases)
- [Commits](https://github.com/reactor/reactor-netty/compare/v1.0.38...v1.0.39)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-16 08:35:05 -03:00
Steve Riesenberg 7335c5745c Document authentication helper method in WebClient integration
This commit re-applies 49f3c0ce53
which was lost while splitting pages for Antora.

Issue gh-13816
Issue gh-10120
2023-11-09 10:46:32 -06:00
Josh Cummings 52675c80b3 Check For Null Exception Message
Closes gh-13768
2023-11-07 17:19:35 -07:00
Josh Cummings b919ece045 Change Idempotent to Read-Only
Closes gh-13644
2023-11-07 16:25:28 -07:00
Josh Cummings 11a21896dd Defer SecurityContextHolderStrategy Lookup
Due to how early method interceptors are loaded during startup
it's reasonable to consider scenarios where applications are
changing the global security context holder strategy during
startup.

Closes gh-12877
2023-11-07 12:36:16 -07:00
Marcus Hert Da Coregio eff9814d7b Add links to WebFlux section where referenced
Closes gh-14100
2023-11-07 13:13:41 -03:00
Dong, Xue-Han 058495d463 Add the Authorization Section to Nav List
Closes gh-14099
2023-11-07 13:11:52 -03:00
Josh Cummings ffd12ee3b9 Refine requestMatcher Validation Rules
Closes gh-14078
2023-10-31 17:08:24 -06:00
Marcus Hert Da Coregio 3f64c6d745 Use version catalog to resolve nimbus dependency versions
Issue gh-14047
2023-10-30 16:11:51 -03:00
Marcus Hert Da Coregio 2cd302fb1b Revert "Use lenient configuration for prohibited dependencies check"
This reverts commit 602d4189d1.
2023-10-30 16:11:02 -03:00
Marcus Hert Da Coregio e8ec49b4c5 Resolve versions without downloading dependencies
Issue gh-14047
2023-10-30 14:46:12 -03:00
Marcus Hert Da Coregio 602d4189d1 Use lenient configuration for prohibited dependencies check
Issue gh-14047
2023-10-30 12:10:32 -03:00
Marcus Hert Da Coregio 884014c2fb Use lenient configuration to resolve artifacts
Issue gh-14047
2023-10-30 11:25:30 -03:00
Marcus Hert Da Coregio 47969ec7c9 Fix verifyDependenciesVersions not working with s101
Issue gh-14047
2023-10-30 08:28:15 -03:00
Marcus Da Coregio 6654479649 Polish VerifyDependenciesVersionsPlugin
Issue gh-14047
2023-10-27 09:45:19 -03:00
Steve Riesenberg bfc31bacab Polish Username/Password Authentication page
Issue gh-11926

(cherry picked from commit 781d575921)
2023-10-25 15:50:53 -05:00
Marcus Da Coregio ad2bea3350 Fail build if nimbus-jose-jwt version is not aligned
Closes gh-14047
2023-10-25 13:44:23 -03:00
Steve Riesenberg 5161712c35 Polish gh-13976
Closes gh-13757
2023-10-19 16:40:23 -05:00
Veli Döngelci a6b872dcf3 Fix caching error state in ReactiveRemoteJWKSource 2023-10-19 16:40:13 -05:00
Marcus Da Coregio 70ad3bf749 relay_state should not be included in signing calculation when it is null
Closes gh-13913
2023-10-19 09:58:47 -03:00
Scott Shidlovsky 19c4e427ee Update OpenSamlAuthenticationRequestResolverTests from Junit 4 to Junit 5
(cherry picked from commit 508f7d7b8a)
2023-10-19 09:50:28 -03:00
Marcus Da Coregio 736f1c27cd Remove updateDependencies plugin
Closes gh-13966
2023-10-18 14:12:25 -03:00
github-actions[bot] ec58bf0b28 Next development version 2023-10-16 16:06:01 +00:00
github-actions[bot] e4e31f2c90 Release 5.8.8 2023-10-16 15:21:39 +00:00
1690 changed files with 42690 additions and 29612 deletions
-4
View File
@@ -7,10 +7,6 @@ assignees: ''
---
<!--
Do NOT report Security Vulnerabilities here. Please use https://github.com/spring-projects/spring-security/security/policy
-->
**Describe the bug**
A clear and concise description of what the bug is.
@@ -31,7 +31,7 @@ jobs:
runjobs: ${{ steps.continue.outputs.runjobs }}
project_version: ${{ steps.continue.outputs.project_version }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- id: continue
name: Determine if should continue
run: |
@@ -40,8 +40,8 @@ jobs:
# Extract version from gradle.properties
version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
echo "project_version=$version" >>$GITHUB_OUTPUT
build_jdk_17:
name: Build JDK 17
build_jdk_11:
name: Build JDK 11
needs: [prerequisites]
strategy:
matrix:
@@ -49,15 +49,15 @@ jobs:
runs-on: ${{ matrix.os }}
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
- uses: actions/checkout@v4
- name: Set up JDK 11
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
java-version: '11'
distribution: 'adopt'
cache: 'gradle'
- name: Set up Gradle
uses: gradle/gradle-build-action@v2
uses: gradle/gradle-build-action@v3
- name: Set up gradle user name
run: echo 'systemProp.user.name=spring-builds+github' >> gradle.properties
- name: Build with Gradle
@@ -72,30 +72,30 @@ jobs:
runs-on: ubuntu-latest
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
uses: spring-io/spring-gradle-build-action@v2
with:
java-version: '17'
distribution: 'temurin'
java-version: '11'
distribution: 'adopt'
- name: Snapshot Tests
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
./gradlew test --refresh-dependencies -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PspringFrameworkVersion='6.0.+' -PreactorVersion='2022.0.+' -PspringDataVersion='2022.0.+' -PlocksDisabled --stacktrace
./gradlew test --refresh-dependencies -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PforceMavenRepositories=snapshot -PisOverrideVersionCatalog -PspringFrameworkVersion='5.+' -PreactorVersion='2020.0.+' -PspringDataVersion='2021.2.+' -PlocksDisabled --stacktrace
check_samples:
name: Check Samples project
needs: [prerequisites]
runs-on: ubuntu-latest
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
uses: spring-io/spring-gradle-build-action@v2
with:
java-version: '17'
distribution: 'temurin'
java-version: '11'
distribution: 'adopt'
- name: Check samples project
env:
LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos
@@ -114,12 +114,12 @@ jobs:
runs-on: ubuntu-latest
if: needs.prerequisites.outputs.runjobs
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
uses: spring-io/spring-gradle-build-action@v2
with:
java-version: '17'
distribution: 'temurin'
java-version: '11'
distribution: 'adopt'
- name: Check for package tangles
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -128,15 +128,15 @@ jobs:
./gradlew check s101 -Ps101.licenseId="$STRUCTURE101_LICENSEID" --stacktrace
deploy_artifacts:
name: Deploy Artifacts
needs: [build_jdk_17, snapshot_tests, check_samples, check_tangles]
needs: [build_jdk_11, snapshot_tests, check_samples, check_tangles]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
uses: spring-io/spring-gradle-build-action@v2
with:
java-version: '17'
distribution: 'temurin'
java-version: '11'
distribution: 'adopt'
- name: Deploy artifacts
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -152,15 +152,15 @@ jobs:
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
deploy_docs:
name: Deploy Docs
needs: [build_jdk_17, snapshot_tests, check_samples, check_tangles]
needs: [build_jdk_11, snapshot_tests, check_samples, check_tangles]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
uses: spring-io/spring-gradle-build-action@v2
with:
java-version: '17'
distribution: 'temurin'
java-version: '11'
distribution: 'adopt'
- name: Deploy Docs
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -173,15 +173,15 @@ jobs:
DOCS_HOST: ${{ secrets.DOCS_HOST }}
deploy_schema:
name: Deploy Schema
needs: [build_jdk_17, snapshot_tests, check_samples, check_tangles]
needs: [build_jdk_11, snapshot_tests, check_samples, check_tangles]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
uses: spring-io/spring-gradle-build-action@v2
with:
java-version: '17'
distribution: 'temurin'
java-version: '11'
distribution: 'adopt'
- name: Deploy Schema
run: |
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
@@ -206,14 +206,14 @@ jobs:
TOKEN: ${{ github.token }}
VERSION: ${{ needs.prerequisites.outputs.project_version }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
uses: spring-io/spring-gradle-build-action@v2
with:
java-version: '17'
distribution: 'temurin'
java-version: '11'
distribution: 'adopt'
- name: Wait for Artifactory Artifacts
if: ${{ contains(needs.prerequisites.outputs.project_version, '-RC') || contains(needs.prerequisites.outputs.project_version, '-M') }}
run: |
@@ -243,7 +243,7 @@ jobs:
./gradlew createGitHubRelease -PnextVersion=$VERSION -Pbranch=$BRANCH -PcreateRelease=true -PgitHubAccessToken=$TOKEN
- name: Announce Release on Slack
id: spring-security-announcing
uses: slackapi/slack-github-action@v1.19.0
uses: slackapi/slack-github-action@v1.25.0
with:
payload: |
{
@@ -287,12 +287,12 @@ jobs:
TOKEN: ${{ github.token }}
VERSION: ${{ needs.prerequisites.outputs.project_version }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
uses: spring-io/spring-gradle-build-action@v2
with:
java-version: '17'
distribution: 'temurin'
java-version: '11'
distribution: 'adopt'
- name: Schedule next release (if not already scheduled)
run: ./gradlew scheduleNextRelease -PnextVersion=$VERSION -PgitHubAccessToken=$TOKEN
notify_result:
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
if: github.repository_owner == 'spring-projects'
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: docs-build
fetch-depth: 1
+52
View File
@@ -0,0 +1,52 @@
name: Merge Dependabot PR
on: pull_request_target
run-name: Merge Dependabot PR ${{ github.ref_name }}
permissions: write-all
jobs:
merge-dependabot-pr:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
- uses: actions/checkout@v4
with:
show-progress: false
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
- name: Set Milestone to Dependabot Pull Request
id: set-milestone
run: |
if test -f pom.xml
then
CURRENT_VERSION=$(mvn help:evaluate -Dexpression="project.version" -q -DforceStdout)
else
CURRENT_VERSION=$(cat gradle.properties | sed -n '/^version=/ { s/^version=//;p }')
fi
export CANDIDATE_VERSION=${CURRENT_VERSION/-SNAPSHOT}
MILESTONE=$(gh api repos/$GITHUB_REPOSITORY/milestones --jq 'map(select(.due_on != null and (.title | startswith(env.CANDIDATE_VERSION)))) | .[0] | .title')
if [ -z $MILESTONE ]
then
gh run cancel ${{ github.run_id }}
echo "::warning title=Cannot merge::No scheduled milestone for $CURRENT_VERSION version"
else
gh pr edit ${{ github.event.pull_request.number }} --milestone $MILESTONE
echo mergeEnabled=true >> $GITHUB_OUTPUT
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Merge Dependabot pull request
if: steps.set-milestone.outputs.mergeEnabled
run: gh pr merge ${{ github.event.pull_request.number }} --auto --rebase
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
@@ -32,7 +32,7 @@ jobs:
actions: read
steps:
- name: Send Slack message
uses: Gamesight/slack-workflow-status@v1.0.1
uses: Gamesight/slack-workflow-status@v1.3.0
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
+9 -4
View File
@@ -2,6 +2,11 @@ name: PR Build
on: pull_request
env:
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
permissions:
contents: read
@@ -11,11 +16,11 @@ jobs:
runs-on: ubuntu-latest
if: ${{ github.repository == 'spring-projects/spring-security' }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
uses: spring-io/spring-gradle-build-action@v2
with:
java-version: '17'
distribution: 'temurin'
java-version: '11'
distribution: 'adopt'
- name: Build with Gradle
run: ./gradlew clean build --continue --scan
-24
View File
@@ -1,24 +0,0 @@
name: Release Scheduler
on:
schedule:
- cron: '15 15 * * MON' # Every Monday at 3:15pm UTC
workflow_dispatch:
permissions: read-all
jobs:
dispatch_scheduled_releases:
name: Dispatch scheduled releases
if: github.repository_owner == 'spring-projects'
strategy:
matrix:
# List of active maintenance branches.
branch: [ main, 6.0.x, 5.8.x, 5.7.x, 5.6.x ]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 1
- name: Dispatch
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: gh workflow run update-scheduled-release-version.yml -r ${{ matrix.branch }}
@@ -0,0 +1,22 @@
name: Trigger Dependabot Auto Merge Forward
on:
push:
branches:
- '*.x'
permissions: read-all
jobs:
trigger-worflow:
name: Trigger Workflow
runs-on: ubuntu-latest
if: ${{ github.event.commits[0].author.username == 'dependabot[bot]' && github.repository == 'spring-projects/spring-security' }}
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v4
- id: trigger
env:
GH_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
run: gh workflow run dependabot-auto-merge-forward.yml -r main
@@ -23,14 +23,14 @@ jobs:
steps:
- id: checkout-source
name: Checkout Source Code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
- name: Set up gradle
uses: spring-io/spring-gradle-build-action@v1
uses: spring-io/spring-gradle-build-action@v2
with:
java-version: '17'
distribution: 'temurin'
java-version: '11'
distribution: 'adopt'
- id: check-release-due
name: Check Release Due
run: |
@@ -72,7 +72,7 @@ jobs:
- id: send-slack-notification
name: Send Slack message
if: failure()
uses: Gamesight/slack-workflow-status@v1.0.1
uses: Gamesight/slack-workflow-status@v1.3.0
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
+1 -1
View File
@@ -3,4 +3,4 @@
# See https://sdkman.io/usage#config
# A summary is to add the following to ~/.sdkman/etc/config
# sdkman_auto_env=true
java=17.0.3-tem
java=11.0.14-tem
-3
View File
@@ -1,3 +0,0 @@
{
"java.import.gradle.enabled": false
}
+8 -19
View File
@@ -6,8 +6,8 @@ image:https://img.shields.io/badge/Revved%20up%20by-Gradle%20Enterprise-06A0CE?l
= Spring Security
Spring Security provides security services for the https://docs.spring.io[Spring IO Platform]. Spring Security 6.0 requires Spring 6.0 as
a minimum and also requires Java 17.
Spring Security provides security services for the https://docs.spring.io[Spring IO Platform]. Spring Security 5.0 requires Spring 5.0 as
a minimum and also requires Java 8.
For a detailed list of features and access to the latest release, please visit https://spring.io/projects[Spring projects].
@@ -30,9 +30,9 @@ In the instructions below, https://vimeo.com/34436402[`./gradlew`] is invoked fr
a cross-platform, self-contained bootstrap mechanism for the build.
=== Prerequisites
https://docs.github.com/en/get-started/quickstart/set-up-git[Git] and the https://www.oracle.com/java/technologies/downloads/#java17[JDK17 build].
https://help.github.com/set-up-git-redirect[Git] and the https://www.oracle.com/technetwork/java/javase/downloads[JDK11 build].
Be sure that your `JAVA_HOME` environment variable points to the `jdk-17` folder extracted from the JDK download.
Be sure that your `JAVA_HOME` environment variable points to the `jdk-11` folder extracted from the JDK download.
=== Check out sources
[indent=0]
@@ -40,38 +40,27 @@ Be sure that your `JAVA_HOME` environment variable points to the `jdk-17` folder
git clone git@github.com:spring-projects/spring-security.git
----
=== Install all `spring-*.jar` into your local Maven repository.
=== Install all spring-\* jars into your local Maven cache
[indent=0]
----
./gradlew publishToMavenLocal
----
=== Compile and test; build all JARs, distribution zips, and docs
=== Compile and test; build all jars, distribution zips, and docs
[indent=0]
----
./gradlew build
----
The reference docs are not currently included in the distribution zip.
You can build the reference docs for this branch by running the following command:
----
./gradlew :spring-security-docs:antora
----
That command publishes the docs site to the `_docs/build/site_` directory.
The https://github.com/spring-projects/spring-security/tree/docs-build[playbook branch] describes how to build the reference docs in detail.
Discover more commands with `./gradlew tasks`.
See also the https://github.com/spring-projects/spring-framework/wiki/Gradle-build-and-release-FAQ[Gradle build and release FAQ].
== Getting Support
Check out the https://stackoverflow.com/questions/tagged/spring-security[Spring Security tags on Stack Overflow].
https://spring.io/support[Commercial support] is available too.
== Contributing
https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request[Pull requests] are welcome; see the https://github.com/spring-projects/spring-security/blob/main/CONTRIBUTING.adoc[contributor guidelines] for details.
https://help.github.com/articles/creating-a-pull-request[Pull requests] are welcome; see the https://github.com/spring-projects/spring-security/blob/main/CONTRIBUTING.adoc[contributor guidelines] for details.
== License
Spring Security is Open Source software released under the
+15 -110
View File
@@ -1,89 +1,4 @@
= Release Process
The release process for Spring Security is partially automated. The following table outlines which steps are automated and which are manual. Follow the links to read about each step.
[cols="1,1"]
|===
| Step | Status
| <<update-dependencies>>
| :x: manual
| <<check-all-issues-are-closed>>
| :white_check_mark: automated (scheduled release will abort if any issues are open)
| <<update-release-version>>
| :white_check_mark: automated
| <<update-antora-version>>
| :white_check_mark: automated
| <<build-locally>>
| :x: manual (when updating dependencies)
| <<push-release-commit>>
| :white_check_mark: automated
| <<announce-release-on-slack>>
| :white_check_mark: automated
| <<tag-release>>
| :white_check_mark: automated
| <<update-to-next-development-version>>
| :white_check_mark: automated
| <<update-version-on-project-page>>
| :x: manual
| <<update-release-notes-on-github>>
| :white_check_mark: automated
| <<close-create-milestone,Close milestone>>
| :x: manual (move issues to new milestone before release)
| <<close-create-milestone,Create milestone>>
| :white_check_mark: automated (if not already created)
| <<announce-release-on-other-channels>>
| :x: manual
|===
*When should I update dependencies manually?* Dependencies should be updated at the latest the end of the week prior to the release. This is usually the Friday following the 2nd Monday of the month (counting from the first week with a Monday). When in doubt, check the https://github.com/spring-projects/spring-security/milestones[milestones] page for release due dates.
*When do scheduled releases occur?* Automated releases are scheduled to occur at *3:15 PM UTC* on the *3rd Monday of the month* (counting from the first week with a Monday).
[NOTE]
The scheduled release process currently runs every Monday but only releases when a release is due. See the performed checks below for more information.
The automated release process occurs on the following branches:
* `main`
* `5.8.x`
* `5.7.x`
* `5.6.x`
For each of the above branches, the automated process performs the following checks before proceeding with the release:
1. _Check if the milestone is due today._ This check compares the current (SNAPSHOT) version of the branch with available milestones and chooses the first match (sorted alphabetically). If the due date on the matched milestone is *not* today, the process stops.
2. _Check if all issues are closed._ This check uses the milestone from the previous step and looks for open issues. If any open issues are found, the process stops.
[IMPORTANT]
You should ensure all issues are closed or moved to another milestone prior to a scheduled release.
If the above checks pass, the version number is updated (in `gradle.properties` and `antora.yml`) and a commit is pushed to trigger the CI process.
*How do I trigger a release manually?* You can trigger a release manually in two ways:
1. Trigger a release for a particular branch via https://github.com/spring-projects/spring-security/actions/workflows/update-scheduled-release-version.yml[`update-scheduled-release-version.yml`] on the desired branch. The above checks are performed for that branch, and the release will proceed if all checks pass. _This is the recommended way to trigger a release that did not pass the above checks during a regularly scheduled release._
2. Trigger releases for all branches via https://github.com/spring-projects/spring-security/actions/workflows/release-scheduler.yml[`release-scheduler.yml`] on the `main` branch. The above checks are performed for each branch, and only releases that pass all checks will proceed.
*When should additional manual steps be performed?* All other automated steps listed above occur during the normal CI process. Additional manual steps can be performed at any time once the builds pass and releases are finished.
*What if something goes wrong?* If the normal CI process fails, you can retry by re-running the failed jobs with the "Re-run failed jobs" option in GitHub Actions. If changes are required, you should revert the "Release x.y.z" commit, delete the tag, and proceed manually.
[#update-dependencies]
== Update dependencies
= Update Dependencies
Ensure you have no changes in your local repository.
Change to a new branch.
@@ -144,8 +59,7 @@ $ ./gradlew updateDependencies -PupdateMode=GITHUB_ISSUE -PgitHubAccessToken=<gi
Apply any fixes from your previous branch that were necessary.
[#check-all-issues-are-closed]
== Check all issues are closed
= Check All Issues are Closed
The following command will check if there are any open issues for the ticket.
Before running the command, replace the following values:
@@ -160,13 +74,11 @@ $ ./gradlew gitHubCheckMilestoneHasNoOpenIssues -PgitHubAccessToken=<github-pers
Alternatively, you can manually check using https://github.com/spring-projects/spring-security/milestones
[#update-release-version]
== Update release version
= Update Release Version
Update the version number in `gradle.properties` for the release, for example `5.5.0-M1`, `5.5.0-RC1`, `5.5.0`
[#update-antora-version]
== Update antora version
= Update Antora Version
You will need to update the antora.yml version.
If you are unsure of what the values should be, the following task will instruct you what the expected values are:
@@ -176,8 +88,7 @@ If you are unsure of what the values should be, the following task will instruct
./gradlew :spring-security-docs:antoraCheckVersion
----
[#build-locally]
== Build locally
= Build Locally
Run the build using
@@ -186,8 +97,7 @@ Run the build using
$ ./gradlew check
----
[#push-release-commit]
== Push release commit
= Push the Release Commit
Push the commit and GitHub actions will build and deploy the artifacts
If you are pushing to Maven Central, then you can get notified when its uploaded by running the following:
@@ -197,8 +107,7 @@ If you are pushing to Maven Central, then you can get notified when its uploa
$ ./scripts/release/wait-for-done.sh 5.5.0
----
[#announce-release-on-slack]
== Announce release on Slack
= Announce the release on Slack
* Announce via Slack on
https://pivotal.slack.com/messages/spring-release[#spring-release],
@@ -209,8 +118,7 @@ Something like:
spring-security-announcing 5.5.0 is available.
....
[#tag-release]
== Tag release
= Tag the release
* Tag the release and then push the tag
@@ -219,13 +127,11 @@ git tag 5.4.0-RC1
git push origin 5.4.0-RC1
....
[#update-to-next-development-version]
== Update to next development version
== 7. Update to Next Development Version
* Update `gradle.properties` version to next `+SNAPSHOT+` version, update antora.yml, and then push
[#update-version-on-project-page]
== Update version on project page
== 8. Update version on project page
The following command will update https://spring.io/projects/spring-security#learn with the new release version using the following parameters
@@ -238,8 +144,9 @@ The following command will update https://spring.io/projects/spring-security#lea
$ ./gradlew saganCreateRelease saganDeleteRelease -PgitHubAccessToken=<github-personal-access-token> -PnextVersion=<next-version> -PpreviousVersion=<previous-version>
----
[#update-release-notes-on-github]
== Update release notes on GitHub
== 9. Update Release Notes on GitHub
Generate the Release Notes replacing:
@@ -260,8 +167,7 @@ cat build/changelog/release-notes.md | xclip -selection clipboard
https://github.com/spring-projects/spring-security/releases[release on
GitHub], associate it with the tag, and paste the generated notes
[#close-create-milestone]
== Close / Create milestone
== 10. Close / Create Milestone
* In
https://github.com/spring-projects/spring-security/milestones[GitHub
@@ -270,8 +176,7 @@ Milestones], create a new milestone for the next release version
the new milestone
* Close the milestone for the release.
[#announce-release-on-other-channels]
== Announce release on other channels
== 11. Announce the release on other channels
* Create a https://spring.io/admin/blog[Blog]
* Tweet from [@SpringSecurity](https://twitter.com/springsecurity)
+2
View File
@@ -9,6 +9,8 @@ dependencies {
api 'org.springframework:spring-jdbc'
api 'org.springframework:spring-tx'
optional 'net.sf.ehcache:ehcache'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-params"
@@ -0,0 +1,141 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.acls.domain;
import java.io.Serializable;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import org.springframework.security.acls.model.AclCache;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.PermissionGrantingStrategy;
import org.springframework.security.util.FieldUtils;
import org.springframework.util.Assert;
/**
* Simple implementation of {@link AclCache} that delegates to EH-CACHE.
* <p>
* Designed to handle the transient fields in {@link AclImpl}. Note that this
* implementation assumes all {@link AclImpl} instances share the same
* {@link PermissionGrantingStrategy} and {@link AclAuthorizationStrategy} instances.
*
* @author Ben Alex
* @deprecated since 5.6. In favor of JCache based implementations
*/
@Deprecated
public class EhCacheBasedAclCache implements AclCache {
private final Ehcache cache;
private PermissionGrantingStrategy permissionGrantingStrategy;
private AclAuthorizationStrategy aclAuthorizationStrategy;
public EhCacheBasedAclCache(Ehcache cache, PermissionGrantingStrategy permissionGrantingStrategy,
AclAuthorizationStrategy aclAuthorizationStrategy) {
Assert.notNull(cache, "Cache required");
Assert.notNull(permissionGrantingStrategy, "PermissionGrantingStrategy required");
Assert.notNull(aclAuthorizationStrategy, "AclAuthorizationStrategy required");
this.cache = cache;
this.permissionGrantingStrategy = permissionGrantingStrategy;
this.aclAuthorizationStrategy = aclAuthorizationStrategy;
}
@Override
public void evictFromCache(Serializable pk) {
Assert.notNull(pk, "Primary key (identifier) required");
MutableAcl acl = getFromCache(pk);
if (acl != null) {
this.cache.remove(acl.getId());
this.cache.remove(acl.getObjectIdentity());
}
}
@Override
public void evictFromCache(ObjectIdentity objectIdentity) {
Assert.notNull(objectIdentity, "ObjectIdentity required");
MutableAcl acl = getFromCache(objectIdentity);
if (acl != null) {
this.cache.remove(acl.getId());
this.cache.remove(acl.getObjectIdentity());
}
}
@Override
public MutableAcl getFromCache(ObjectIdentity objectIdentity) {
Assert.notNull(objectIdentity, "ObjectIdentity required");
try {
Element element = this.cache.get(objectIdentity);
return (element != null) ? initializeTransientFields((MutableAcl) element.getValue()) : null;
}
catch (CacheException ex) {
return null;
}
}
@Override
public MutableAcl getFromCache(Serializable pk) {
Assert.notNull(pk, "Primary key (identifier) required");
try {
Element element = this.cache.get(pk);
return (element != null) ? initializeTransientFields((MutableAcl) element.getValue()) : null;
}
catch (CacheException ex) {
return null;
}
}
@Override
public void putInCache(MutableAcl acl) {
Assert.notNull(acl, "Acl required");
Assert.notNull(acl.getObjectIdentity(), "ObjectIdentity required");
Assert.notNull(acl.getId(), "ID required");
if (this.aclAuthorizationStrategy == null) {
if (acl instanceof AclImpl) {
this.aclAuthorizationStrategy = (AclAuthorizationStrategy) FieldUtils
.getProtectedFieldValue("aclAuthorizationStrategy", acl);
this.permissionGrantingStrategy = (PermissionGrantingStrategy) FieldUtils
.getProtectedFieldValue("permissionGrantingStrategy", acl);
}
}
if ((acl.getParentAcl() != null) && (acl.getParentAcl() instanceof MutableAcl)) {
putInCache((MutableAcl) acl.getParentAcl());
}
this.cache.put(new Element(acl.getObjectIdentity(), acl));
this.cache.put(new Element(acl.getId(), acl));
}
private MutableAcl initializeTransientFields(MutableAcl value) {
if (value instanceof AclImpl) {
FieldUtils.setProtectedFieldValue("aclAuthorizationStrategy", value, this.aclAuthorizationStrategy);
FieldUtils.setProtectedFieldValue("permissionGrantingStrategy", value, this.permissionGrantingStrategy);
}
if (value.getParentAcl() != null) {
initializeTransientFields((MutableAcl) value.getParentAcl());
}
return value;
}
@Override
public void clearCache() {
this.cache.removeAll();
}
}
@@ -16,7 +16,6 @@
package org.springframework.security.acls.jdbc;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -24,15 +23,15 @@ import java.util.UUID;
import javax.sql.DataSource;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.acls.TargetObject;
import org.springframework.security.acls.TargetObjectWithUUID;
@@ -42,10 +41,10 @@ import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.ConsoleAuditLogger;
import org.springframework.security.acls.domain.DefaultPermissionFactory;
import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy;
import org.springframework.security.acls.domain.EhCacheBasedAclCache;
import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.domain.SpringCacheBasedAclCache;
import org.springframework.security.acls.model.Acl;
import org.springframework.security.acls.model.AuditableAccessControlEntry;
import org.springframework.security.acls.model.MutableAcl;
@@ -56,8 +55,6 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests {@link BasicLookupStrategy}
@@ -78,7 +75,7 @@ public abstract class AbstractBasicLookupStrategyTests {
private BasicLookupStrategy strategy;
private static CacheManagerMock cacheManager;
private static CacheManager cacheManager;
public abstract JdbcTemplate getJdbcTemplate();
@@ -86,13 +83,14 @@ public abstract class AbstractBasicLookupStrategyTests {
@BeforeAll
public static void initCacheManaer() {
cacheManager = new CacheManagerMock();
cacheManager.addCache("basiclookuptestcache");
cacheManager = CacheManager.create();
cacheManager.addCache(new Cache("basiclookuptestcache", 500, false, false, 30, 30));
}
@AfterAll
public static void shutdownCacheManager() {
cacheManager.clear();
cacheManager.removalAll();
cacheManager.shutdown();
}
@BeforeEach
@@ -120,17 +118,11 @@ public abstract class AbstractBasicLookupStrategyTests {
return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ADMINISTRATOR"));
}
protected SpringCacheBasedAclCache aclCache() {
return new SpringCacheBasedAclCache(getCache(), new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()),
protected EhCacheBasedAclCache aclCache() {
return new EhCacheBasedAclCache(getCache(), new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()),
new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_USER")));
}
protected Cache getCache() {
Cache cache = cacheManager.getCacheManager().getCache("basiclookuptestcache");
cache.clear();
return cache;
}
@AfterEach
public void emptyDatabase() {
String query = "DELETE FROM acl_entry;" + "DELETE FROM acl_object_identity WHERE ID = 9;"
@@ -142,6 +134,12 @@ public abstract class AbstractBasicLookupStrategyTests {
getJdbcTemplate().execute(query);
}
protected Ehcache getCache() {
Ehcache cache = cacheManager.getCache("basiclookuptestcache");
cache.removeAll();
return cache;
}
@Test
public void testAclsRetrievalWithDefaultBatchSize() throws Exception {
ObjectIdentity topParentOid = new ObjectIdentityImpl(TARGET_CLASS, 100L);
@@ -329,32 +327,4 @@ public abstract class AbstractBasicLookupStrategyTests {
// @formatter:on
}
private static final class CacheManagerMock {
private final List<String> cacheNames;
private final CacheManager cacheManager;
private CacheManagerMock() {
this.cacheNames = new ArrayList<>();
this.cacheManager = mock(CacheManager.class);
given(this.cacheManager.getCacheNames()).willReturn(this.cacheNames);
}
private CacheManager getCacheManager() {
return this.cacheManager;
}
private void addCache(String name) {
this.cacheNames.add(name);
Cache cache = new ConcurrentMapCache(name);
given(this.cacheManager.getCache(name)).willReturn(cache);
}
private void clear() {
this.cacheNames.clear();
}
}
}
@@ -0,0 +1,223 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.acls.jdbc;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.List;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.acls.domain.AclAuthorizationStrategy;
import org.springframework.security.acls.domain.AclAuthorizationStrategyImpl;
import org.springframework.security.acls.domain.AclImpl;
import org.springframework.security.acls.domain.ConsoleAuditLogger;
import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy;
import org.springframework.security.acls.domain.EhCacheBasedAclCache;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.util.FieldUtils;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests {@link EhCacheBasedAclCache}
*
* @author Andrei Stefan
*/
@ExtendWith(MockitoExtension.class)
public class EhCacheBasedAclCacheTests {
private static final String TARGET_CLASS = "org.springframework.security.acls.TargetObject";
@Mock
private Ehcache cache;
@Captor
private ArgumentCaptor<Element> element;
private EhCacheBasedAclCache myCache;
private MutableAcl acl;
@BeforeEach
public void setup() {
this.myCache = new EhCacheBasedAclCache(this.cache,
new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()),
new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_USER")));
ObjectIdentity identity = new ObjectIdentityImpl(TARGET_CLASS, 100L);
AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"),
new SimpleGrantedAuthority("ROLE_GENERAL"));
this.acl = new AclImpl(identity, 1L, aclAuthorizationStrategy, new ConsoleAuditLogger());
}
@AfterEach
public void cleanup() {
SecurityContextHolder.clearContext();
}
@Test
public void constructorRejectsNullParameters() {
assertThatIllegalArgumentException().isThrownBy(
() -> new EhCacheBasedAclCache(null, new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger()),
new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_USER"))));
}
@Test
public void methodsRejectNullParameters() {
assertThatIllegalArgumentException().isThrownBy(() -> this.myCache.evictFromCache((Serializable) null));
assertThatIllegalArgumentException().isThrownBy(() -> this.myCache.evictFromCache((ObjectIdentity) null));
assertThatIllegalArgumentException().isThrownBy(() -> this.myCache.getFromCache((Serializable) null));
assertThatIllegalArgumentException().isThrownBy(() -> this.myCache.getFromCache((ObjectIdentity) null));
assertThatIllegalArgumentException().isThrownBy(() -> this.myCache.putInCache(null));
}
// SEC-527
@Test
public void testDiskSerializationOfMutableAclObjectInstance() throws Exception {
// Serialization test
File file = File.createTempFile("SEC_TEST", ".object");
FileOutputStream fos = new FileOutputStream(file);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(this.acl);
oos.close();
FileInputStream fis = new FileInputStream(file);
ObjectInputStream ois = new ObjectInputStream(fis);
MutableAcl retrieved = (MutableAcl) ois.readObject();
ois.close();
assertThat(retrieved).isEqualTo(this.acl);
Object retrieved1 = FieldUtils.getProtectedFieldValue("aclAuthorizationStrategy", retrieved);
assertThat(retrieved1).isNull();
Object retrieved2 = FieldUtils.getProtectedFieldValue("permissionGrantingStrategy", retrieved);
assertThat(retrieved2).isNull();
}
@Test
public void clearCache() {
this.myCache.clearCache();
verify(this.cache).removeAll();
}
@Test
public void putInCache() {
this.myCache.putInCache(this.acl);
verify(this.cache, times(2)).put(this.element.capture());
assertThat(this.element.getValue().getKey()).isEqualTo(this.acl.getId());
assertThat(this.element.getValue().getObjectValue()).isEqualTo(this.acl);
assertThat(this.element.getAllValues().get(0).getKey()).isEqualTo(this.acl.getObjectIdentity());
assertThat(this.element.getAllValues().get(0).getObjectValue()).isEqualTo(this.acl);
}
@Test
public void putInCacheAclWithParent() {
Authentication auth = new TestingAuthenticationToken("user", "password", "ROLE_GENERAL");
auth.setAuthenticated(true);
SecurityContextHolder.getContext().setAuthentication(auth);
ObjectIdentity identityParent = new ObjectIdentityImpl(TARGET_CLASS, 2L);
AclAuthorizationStrategy aclAuthorizationStrategy = new AclAuthorizationStrategyImpl(
new SimpleGrantedAuthority("ROLE_OWNERSHIP"), new SimpleGrantedAuthority("ROLE_AUDITING"),
new SimpleGrantedAuthority("ROLE_GENERAL"));
MutableAcl parentAcl = new AclImpl(identityParent, 2L, aclAuthorizationStrategy, new ConsoleAuditLogger());
this.acl.setParent(parentAcl);
this.myCache.putInCache(this.acl);
verify(this.cache, times(4)).put(this.element.capture());
List<Element> allValues = this.element.getAllValues();
assertThat(allValues.get(0).getKey()).isEqualTo(parentAcl.getObjectIdentity());
assertThat(allValues.get(0).getObjectValue()).isEqualTo(parentAcl);
assertThat(allValues.get(1).getKey()).isEqualTo(parentAcl.getId());
assertThat(allValues.get(1).getObjectValue()).isEqualTo(parentAcl);
assertThat(allValues.get(2).getKey()).isEqualTo(this.acl.getObjectIdentity());
assertThat(allValues.get(2).getObjectValue()).isEqualTo(this.acl);
assertThat(allValues.get(3).getKey()).isEqualTo(this.acl.getId());
assertThat(allValues.get(3).getObjectValue()).isEqualTo(this.acl);
}
@Test
public void getFromCacheSerializable() {
given(this.cache.get(this.acl.getId())).willReturn(new Element(this.acl.getId(), this.acl));
assertThat(this.myCache.getFromCache(this.acl.getId())).isEqualTo(this.acl);
}
@Test
public void getFromCacheSerializablePopulatesTransient() {
given(this.cache.get(this.acl.getId())).willReturn(new Element(this.acl.getId(), this.acl));
this.myCache.putInCache(this.acl);
ReflectionTestUtils.setField(this.acl, "permissionGrantingStrategy", null);
ReflectionTestUtils.setField(this.acl, "aclAuthorizationStrategy", null);
MutableAcl fromCache = this.myCache.getFromCache(this.acl.getId());
assertThat(ReflectionTestUtils.getField(fromCache, "aclAuthorizationStrategy")).isNotNull();
assertThat(ReflectionTestUtils.getField(fromCache, "permissionGrantingStrategy")).isNotNull();
}
@Test
public void getFromCacheObjectIdentity() {
given(this.cache.get(this.acl.getId())).willReturn(new Element(this.acl.getId(), this.acl));
assertThat(this.myCache.getFromCache(this.acl.getId())).isEqualTo(this.acl);
}
@Test
public void getFromCacheObjectIdentityPopulatesTransient() {
given(this.cache.get(this.acl.getObjectIdentity())).willReturn(new Element(this.acl.getId(), this.acl));
this.myCache.putInCache(this.acl);
ReflectionTestUtils.setField(this.acl, "permissionGrantingStrategy", null);
ReflectionTestUtils.setField(this.acl, "aclAuthorizationStrategy", null);
MutableAcl fromCache = this.myCache.getFromCache(this.acl.getObjectIdentity());
assertThat(ReflectionTestUtils.getField(fromCache, "aclAuthorizationStrategy")).isNotNull();
assertThat(ReflectionTestUtils.getField(fromCache, "permissionGrantingStrategy")).isNotNull();
}
@Test
public void evictCacheSerializable() {
given(this.cache.get(this.acl.getObjectIdentity())).willReturn(new Element(this.acl.getId(), this.acl));
this.myCache.evictFromCache(this.acl.getObjectIdentity());
verify(this.cache).remove(this.acl.getId());
verify(this.cache).remove(this.acl.getObjectIdentity());
}
@Test
public void evictCacheObjectIdentity() {
given(this.cache.get(this.acl.getId())).willReturn(new Element(this.acl.getId(), this.acl));
this.myCache.evictFromCache(this.acl.getId());
verify(this.cache).remove(this.acl.getId());
verify(this.cache).remove(this.acl.getObjectIdentity());
}
}
@@ -13,10 +13,16 @@
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="aclCache" class="org.springframework.security.acls.domain.SpringCacheBasedAclCache">
<bean id="aclCache" class="org.springframework.security.acls.domain.EhCacheBasedAclCache">
<constructor-arg>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
<property name="name" value="aclCache"/>
<bean class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager">
<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<!-- This context is used in two tests so accept existing cache manager as the context will be reused -->
<property name="acceptExisting" value="true"/>
</bean>
</property>
<property name="cacheName" value="aclCache"/>
</bean>
</constructor-arg>
<constructor-arg>
-9
View File
@@ -1,15 +1,6 @@
apply plugin: 'io.spring.convention.spring-module'
apply plugin: 'io.freefair.aspectj'
compileAspectj {
sourceCompatibility "17"
targetCompatibility "17"
}
compileTestAspectj {
sourceCompatibility "17"
targetCompatibility "17"
}
dependencies {
management platform(project(":spring-security-dependencies"))
api "org.aspectj:aspectjrt"
+5 -41
View File
@@ -10,7 +10,7 @@ buildscript {
classpath libs.com.netflix.nebula.nebula.project.plugin
}
repositories {
maven { url 'https://plugins.gradle.org/m2/' }
gradlePluginPortal()
}
}
@@ -19,12 +19,12 @@ apply plugin: 'locks'
apply plugin: 's101'
apply plugin: 'io.spring.convention.root'
apply plugin: 'org.jetbrains.kotlin.jvm'
apply plugin: 'org.springframework.security.update-dependencies'
apply plugin: 'org.springframework.security.update-version'
apply plugin: 'org.springframework.security.sagan'
apply plugin: 'org.springframework.github.milestone'
apply plugin: 'org.springframework.github.changelog'
apply plugin: 'org.springframework.github.release'
apply plugin: 'org.springframework.security.versions.verify-dependencies-versions'
group = 'org.springframework.security'
description = 'Spring Security'
@@ -86,46 +86,9 @@ tasks.named("dispatchGitHubWorkflow") {
}
}
tasks.named("updateDependencies") {
// we aren't Gradle 7 compatible yet
checkForGradleUpdate = false
}
updateDependenciesSettings {
gitHub {
organization = "spring-projects"
repository = "spring-security"
}
addFiles({
return [
project.file("buildSrc/src/main/groovy/io/spring/gradle/convention/CheckstylePlugin.groovy")
]
})
dependencyExcludes {
majorVersionBump()
minorVersionBump()
alphaBetaVersions()
snapshotVersions()
addRule { components ->
components.withModule("org.python:jython") { selection ->
ModuleComponentIdentifier candidate = selection.getCandidate();
if (!candidate.getVersion().equals(selection.getCurrentVersion())) {
selection.reject("jython updates break integration tests");
}
}
components.withModule("com.nimbusds:nimbus-jose-jwt") { selection ->
ModuleComponentIdentifier candidate = selection.getCandidate();
if (!candidate.getVersion().equals(selection.getCurrentVersion())) {
selection.reject("nimbus-jose-jwt gets updated when oauth2-oidc-sdk is updated to ensure consistency");
}
}
}
}
}
subprojects {
plugins.withType(JavaPlugin) {
project.sourceCompatibility=JavaVersion.VERSION_17
project.sourceCompatibility='1.8'
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
@@ -161,7 +124,7 @@ allprojects {
tasks.withType(JavaCompile).configureEach {
javaCompiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(17)
languageVersion = JavaLanguageVersion.of(8)
}
}
}
@@ -185,5 +148,6 @@ tasks.register('cloneSamples', IncludeRepoTask) {
}
s101 {
repository = 'https://structure101.com/binaries/latest'
configurationDirectory = project.file("etc/s101")
}
+7 -7
View File
@@ -2,10 +2,9 @@ plugins {
id "java-gradle-plugin"
id "java"
id "groovy"
id 'com.apollographql.apollo' version '2.4.5'
}
sourceCompatibility = JavaVersion.VERSION_17
sourceCompatibility = 1.8
repositories {
gradlePluginPortal()
@@ -41,10 +40,6 @@ gradlePlugin {
id = "io.spring.convention.management-configuration"
implementationClass = "io.spring.gradle.convention.ManagementConfigurationPlugin"
}
updateDependencies {
id = "org.springframework.security.update-dependencies"
implementationClass = "org.springframework.security.convention.versions.UpdateDependenciesPlugin"
}
updateProjectVersion {
id = "org.springframework.security.update-version"
implementationClass = "org.springframework.security.convention.versions.UpdateProjectVersionPlugin"
@@ -69,6 +64,10 @@ gradlePlugin {
id = "s101"
implementationClass = "s101.S101Plugin"
}
verifyDependenciesVersions {
id = "org.springframework.security.versions.verify-dependencies-versions"
implementationClass = "org.springframework.security.convention.versions.VerifyDependenciesVersionsPlugin"
}
}
}
@@ -90,7 +89,6 @@ dependencies {
implementation libs.io.github.gradle.nexus.publish.plugin
implementation 'io.projectreactor:reactor-core'
implementation libs.org.gretty.gretty
implementation libs.com.apollographql.apollo.apollo.runtime
implementation libs.com.github.ben.manes.gradle.versions.plugin
implementation libs.com.github.spullara.mustache.java.compiler
implementation libs.io.spring.javaformat.spring.javaformat.gradle.plugin
@@ -99,6 +97,7 @@ dependencies {
implementation libs.org.hidetake.gradle.ssh.plugin
implementation libs.org.jfrog.buildinfo.build.info.extractor.gradle
implementation libs.org.sonarsource.scanner.gradle.sonarqube.gradle.plugin
implementation libs.com.squareup.okhttp3.okhttp
testImplementation platform(libs.org.junit.junit.bom)
testImplementation platform(libs.org.mockito.mockito.bom)
@@ -112,6 +111,7 @@ dependencies {
testImplementation libs.com.squareup.okhttp3.mockwebserver
}
tasks.named('test', Test).configure {
onlyIf { !project.hasProperty("buildSrc.skipTests") }
useJUnitPlatform()
@@ -54,7 +54,6 @@ public abstract class AbstractSpringJavaPlugin implements Plugin<Project> {
pluginManager.apply("io.spring.convention.javadoc-options");
pluginManager.apply("io.spring.convention.checkstyle");
pluginManager.apply(CopyPropertiesPlugin);
pluginManager.apply("io.spring.convention.eclipse");
project.jar {
manifest.attributes["Created-By"] =
@@ -18,7 +18,6 @@ package io.spring.gradle.convention
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.api.plugins.JavaPlugin
/**
@@ -32,14 +31,12 @@ class CheckstylePlugin implements Plugin<Project> {
@Override
void apply(Project project) {
def versionCatalog = project.rootProject.extensions.getByType(VersionCatalogsExtension.class)
.named("libs")
project.plugins.withType(JavaPlugin) {
def checkstyleDir = project.rootProject.file(CHECKSTYLE_DIR)
if (checkstyleDir.exists() && checkstyleDir.directory) {
project.getPluginManager().apply('checkstyle')
project.dependencies.add('checkstyle', versionCatalog.findLibrary('io-spring-javaformat-spring-javaformat-checkstyle').get())
project.dependencies.add('checkstyle', versionCatalog.findLibrary('io-spring-nohttp-nohttp-checkstyle').get())
project.dependencies.add('checkstyle', 'io.spring.javaformat:spring-javaformat-checkstyle:0.0.15')
project.dependencies.add('checkstyle', 'io.spring.nohttp:nohttp-checkstyle:0.0.11')
project.checkstyle {
configDirectory = checkstyleDir
@@ -1,72 +0,0 @@
/*
* Copyright 2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.spring.gradle.convention
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.plugins.ide.eclipse.EclipsePlugin
/**
* Plugin to tweak .classpath settings so that eclipse/sts/vscode can
* be imported cleanly.
*
* @author Janne Valkealahti
*/
class EclipsePlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.plugins.apply(EclipsePlugin)
project.eclipse {
classpath {
file {
whenMerged {
// for aspects gradle eclipse integration wrongly creates lib entries for
// aspects/build/classes/java/main and aspects/build/resources/main which
// never has anything. eclipse/sts don't care, vscode language server
// complains about missing folders. So remove those from a .classpath
entries.removeAll {
entry -> entry.kind == 'lib' && (entry.path.contains('aspects/build/classes/java/main') || entry.path.contains('aspects/build/resources/main'))
}
// there are cycles and then dependencies between projects main sources and
// test sources. Looks like gradle eclipse integration is getting confused.
// thus just relax rules so that projects always sees everything from
// dependant project.
entries
.findAll { entry -> entry instanceof org.gradle.plugins.ide.eclipse.model.ProjectDependency }
.each {
it.entryAttributes['without_test_code'] = 'false'
it.entryAttributes['test'] = 'false'
}
}
}
}
jdt {
file {
withProperties { properties ->
// there are cycles and then dependencies between projects main sources and
// test sources. Relax those from error to warning
properties['org.eclipse.jdt.core.circularClasspath'] = 'warning'
properties['org.eclipse.jdt.core.incompleteClasspath'] = 'warning'
}
}
}
}
}
}
@@ -52,15 +52,6 @@ public class IntegrationTestPlugin implements Plugin<Project> {
// ensure we don't add if no tests to avoid adding Gretty
return
}
project.sourceSets {
integrationTest {
java.srcDir project.file('src/integration-test/java')
resources.srcDir project.file('src/integration-test/resources')
compileClasspath = project.sourceSets.main.output + project.sourceSets.test.output + project.configurations.integrationTestCompileClasspath
runtimeClasspath = output + compileClasspath + project.configurations.integrationTestRuntimeClasspath
}
}
project.configurations {
integrationTestCompile {
extendsFrom testImplementation
@@ -70,9 +61,20 @@ public class IntegrationTestPlugin implements Plugin<Project> {
}
integrationTestCompileClasspath {
extendsFrom integrationTestCompile
canBeResolved = true
}
integrationTestRuntimeClasspath {
extendsFrom integrationTestRuntime
canBeResolved = true
}
}
project.sourceSets {
integrationTest {
java.srcDir project.file('src/integration-test/java')
resources.srcDir project.file('src/integration-test/resources')
compileClasspath = project.sourceSets.main.output + project.sourceSets.test.output + project.configurations.integrationTestCompileClasspath
runtimeClasspath = output + compileClasspath + project.configurations.integrationTestRuntimeClasspath
}
}
@@ -34,7 +34,7 @@ class JacocoPlugin implements Plugin<Project> {
project.tasks.check.dependsOn project.tasks.jacocoTestReport
project.jacoco {
toolVersion = '0.8.9'
toolVersion = '0.8.2'
}
}
}
@@ -18,6 +18,7 @@ package io.spring.gradle.convention;
import org.gradle.api.Project
import org.gradle.api.plugins.JavaLibraryPlugin;
import org.gradle.api.plugins.MavenPlugin;
import org.gradle.api.plugins.PluginManager
import org.springframework.gradle.classpath.CheckClasspathForProhibitedDependenciesPlugin;
import org.springframework.gradle.maven.SpringMavenPlugin;
@@ -43,7 +43,7 @@ public class TestsConfigurationPlugin implements Plugin<Project> {
}
project.tasks.create('testJar', Jar) {
archiveClassifier = 'test'
classifier = 'test'
from project.sourceSets.test.output
}
@@ -17,14 +17,10 @@
package org.springframework.gradle.github.milestones;
import java.io.IOException;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.util.List;
import java.util.Optional;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -70,7 +70,7 @@ public abstract class GitHubMilestoneHasNoOpenIssuesTask extends DefaultTask {
long milestoneNumber = this.milestones.findMilestoneNumberByTitle(this.repository, this.milestoneTitle);
boolean isOpenIssues = this.milestones.isOpenIssuesForMilestoneNumber(this.repository, milestoneNumber);
Path isOpenIssuesPath = getIsOpenIssuesFile().getAsFile().get().toPath();
Files.writeString(isOpenIssuesPath, String.valueOf(isOpenIssues));
Files.write(isOpenIssuesPath, String.valueOf(isOpenIssues).getBytes());
if (isOpenIssues) {
System.out.println("The repository " + this.repository + " has open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber);
}
@@ -16,13 +16,10 @@
package org.springframework.gradle.github.milestones;
import org.gradle.api.Action;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.tasks.TaskProvider;
import org.springframework.gradle.github.RepositoryRef;
public class GitHubMilestonePlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
@@ -19,7 +19,6 @@ package org.springframework.gradle.github.milestones;
import com.google.gson.annotations.SerializedName;
import java.time.LocalDateTime;
import java.util.Date;
/**
* @author Steve Riesenberg
@@ -1,49 +0,0 @@
/*
* Copyright 2019-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.convention.versions;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Scanner;
class CommandLineUtils {
static void runCommand(File dir, String... args) {
try {
Process process = new ProcessBuilder()
.directory(dir)
.command(args)
.start();
writeLinesTo(process.getInputStream(), System.out);
writeLinesTo(process.getErrorStream(), System.out);
if (process.waitFor() != 0) {
new RuntimeException("Failed to run " + Arrays.toString(args));
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException("Failed to run " + Arrays.toString(args), e);
}
}
private static void writeLinesTo(InputStream input, PrintStream out) {
Scanner scanner = new Scanner(input);
while(scanner.hasNextLine()) {
out.println(scanner.nextLine());
}
}
}
@@ -1,179 +0,0 @@
package org.springframework.security.convention.versions;
import com.apollographql.apollo.ApolloCall;
import com.apollographql.apollo.ApolloClient;
import com.apollographql.apollo.api.Input;
import com.apollographql.apollo.api.Response;
import com.apollographql.apollo.exception.ApolloException;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import org.jetbrains.annotations.NotNull;
import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;
import reactor.util.retry.RetrySpec;
import java.io.IOException;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class GitHubApi {
private final ApolloClient apolloClient;
public GitHubApi(String githubToken) {
if (githubToken == null) {
throw new IllegalArgumentException("githubToken is required. You can set it using -PgitHubAccessToken=");
}
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
clientBuilder.addInterceptor(new AuthorizationInterceptor(githubToken));
this.apolloClient = ApolloClient.builder()
.serverUrl("https://api.github.com/graphql")
.okHttpClient(clientBuilder.build())
.build();
}
public Mono<FindCreateIssueResult> findCreateIssueInput(String owner, String name, String milestone) {
String label = "\"type: dependency-upgrade\"";
FindCreateIssueInputQuery findCreateIssueInputQuery = new FindCreateIssueInputQuery(owner, name, Input.optional(label), Input.optional(milestone));
return Mono.create( sink -> this.apolloClient.query(findCreateIssueInputQuery)
.enqueue(new ApolloCall.Callback<FindCreateIssueInputQuery.Data>() {
@Override
public void onResponse(@NotNull Response<FindCreateIssueInputQuery.Data> response) {
if (response.hasErrors()) {
sink.error(new RuntimeException(response.getErrors().stream().map(e -> e.getMessage()).collect(Collectors.joining(" "))));
} else {
FindCreateIssueInputQuery.Data data = response.getData();
FindCreateIssueInputQuery.Repository repository = data.repository();
List<String> labels = repository.labels().nodes().stream().map(FindCreateIssueInputQuery.Node::id).collect(Collectors.toList());
if (labels.isEmpty()) {
sink.error(new IllegalArgumentException("Could not find label for " + label));
return;
}
Optional<String> firstMilestoneId = repository.milestones().nodes().stream().map(FindCreateIssueInputQuery.Node1::id).findFirst();
if (!firstMilestoneId.isPresent()) {
sink.error(new IllegalArgumentException("Could not find OPEN milestone id for " + milestone));
return;
}
String milestoneId = firstMilestoneId.get();
String repositoryId = repository.id();
String assigneeId = data.viewer().id();
sink.success(new FindCreateIssueResult(repositoryId, labels, milestoneId, assigneeId));
}
}
@Override
public void onFailure(@NotNull ApolloException e) {
sink.error(e);
}
}));
}
public static class FindCreateIssueResult {
private final String repositoryId;
private final List<String> labelIds;
private final String milestoneId;
private final String assigneeId;
public FindCreateIssueResult(String repositoryId, List<String> labelIds, String milestoneId, String assigneeId) {
this.repositoryId = repositoryId;
this.labelIds = labelIds;
this.milestoneId = milestoneId;
this.assigneeId = assigneeId;
}
public String getRepositoryId() {
return repositoryId;
}
public List<String> getLabelIds() {
return labelIds;
}
public String getMilestoneId() {
return milestoneId;
}
public String getAssigneeId() {
return assigneeId;
}
}
public Mono<RateLimitQuery.RateLimit> findRateLimit() {
return Mono.create( sink -> this.apolloClient.query(new RateLimitQuery())
.enqueue(new ApolloCall.Callback<RateLimitQuery.Data>() {
@Override
public void onResponse(@NotNull Response<RateLimitQuery.Data> response) {
if (response.hasErrors()) {
sink.error(new RuntimeException(response.getErrors().stream().map(e -> e.getMessage()).collect(Collectors.joining(" "))));
} else {
sink.success(response.getData().rateLimit());
}
}
@Override
public void onFailure(@NotNull ApolloException e) {
sink.error(e);
}
}));
}
public Mono<Integer> createIssue(String repositoryId, String title, List<String> labelIds, String milestoneId, String assigneeId) {
CreateIssueInputMutation createIssue = new CreateIssueInputMutation.Builder()
.repositoryId(repositoryId)
.title(title)
.labelIds(labelIds)
.milestoneId(milestoneId)
.assigneeId(assigneeId)
.build();
return Mono.create( sink -> this.apolloClient.mutate(createIssue)
.enqueue(new ApolloCall.Callback<CreateIssueInputMutation.Data>() {
@Override
public void onResponse(@NotNull Response<CreateIssueInputMutation.Data> response) {
if (response.hasErrors()) {
String message = response.getErrors().stream().map(e -> e.getMessage() + " " + e.getCustomAttributes() + " " + e.getLocations()).collect(Collectors.joining(" "));
if (message.contains("was submitted too quickly")) {
sink.error(new SubmittedTooQuick(message));
} else {
sink.error(new RuntimeException(message));
}
} else {
sink.success(response.getData().createIssue().issue().number());
}
}
@Override
public void onFailure(@NotNull ApolloException e) {
sink.error(e);
}
}))
.retryWhen(
RetrySpec.fixedDelay(3, Duration.ofMinutes(1))
.filter(SubmittedTooQuick.class::isInstance)
.doBeforeRetry(r -> System.out.println("Pausing for 1 minute and then retrying due to receiving \"submitted too quickly\" error from GitHub API"))
)
.cast(Integer.class);
}
public static class SubmittedTooQuick extends RuntimeException {
public SubmittedTooQuick(String message) {
super(message);
}
}
private static class AuthorizationInterceptor implements Interceptor {
private final String token;
public AuthorizationInterceptor(String token) {
this.token = token;
}
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer " + this.token).build();
return chain.proceed(request);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2020 the original author or authors.
* Copyright 2002-2023 the original author 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,198 +0,0 @@
package org.springframework.security.convention.versions;
import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ComponentSelectionRulesWithCurrent;
import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ComponentSelectionWithCurrent;
import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ResolutionStrategyWithCurrent;
import org.gradle.api.Action;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import java.util.regex.Pattern;
public class UpdateDependenciesExtension {
private Supplier<List<File>> files;
private UpdateMode updateMode = UpdateMode.COMMIT;
private DependencyExcludes dependencyExcludes = new DependencyExcludes();
private GitHub gitHub = new GitHub();
public UpdateDependenciesExtension(Supplier<List<File>> files) {
this.files = files;
}
public void setUpdateMode(UpdateMode updateMode) {
this.updateMode = updateMode;
}
public UpdateMode getUpdateMode() {
return updateMode;
}
GitHub getGitHub() {
return this.gitHub;
}
DependencyExcludes getExcludes() {
return dependencyExcludes;
}
Supplier<List<File>> getFiles() {
return files;
}
public void setFiles(Supplier<List<File>> files) {
this.files = files;
}
public void addFiles(Supplier<List<File>> files) {
Supplier<List<File>> original = this.files;
setFiles(() -> {
List<File> result = new ArrayList<>(original.get());
result.addAll(files.get());
return result;
});
}
public void dependencyExcludes(Action<DependencyExcludes> excludes) {
excludes.execute(this.dependencyExcludes);
}
public void gitHub(Action<GitHub> gitHub) {
gitHub.execute(this.gitHub);
}
public enum UpdateMode {
COMMIT,
GITHUB_ISSUE
}
public class GitHub {
private String organization;
private String repository;
private String accessToken;
private String milestone;
public String getOrganization() {
return organization;
}
public void setOrganization(String organization) {
this.organization = organization;
}
public String getRepository() {
return repository;
}
public void setRepository(String repository) {
this.repository = repository;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getMilestone() {
return milestone;
}
public void setMilestone(String milestone) {
this.milestone = milestone;
}
}
/**
* Consider creating some Predicates instead since they are composible
*/
public class DependencyExcludes {
private List<Action<ComponentSelectionWithCurrent>> actions = new ArrayList<>();
private List<Action<ComponentSelectionRulesWithCurrent>> components = new ArrayList<>();
List<Action<ComponentSelectionWithCurrent>> getActions() {
return actions;
}
public List<Action<ComponentSelectionRulesWithCurrent>> getComponents() {
return components;
}
public DependencyExcludes alphaBetaVersions() {
this.actions.add(excludeVersionWithRegex("(?i).*?(alpha|beta).*", "an alpha or beta version"));
return this;
}
public DependencyExcludes majorVersionBump() {
this.actions.add((selection) -> {
String currentVersion = selection.getCurrentVersion();
int separator = currentVersion.indexOf(".");
String major = separator > 0 ? currentVersion.substring(0, separator) : currentVersion;
String candidateVersion = selection.getCandidate().getVersion();
Pattern calVerPattern = Pattern.compile("\\d\\d\\d\\d.*");
boolean isCalVer = calVerPattern.matcher(candidateVersion).matches();
if (!isCalVer && !candidateVersion.startsWith(major)) {
selection.reject("Cannot upgrade to new Major Version");
}
});
return this;
}
public DependencyExcludes minorVersionBump() {
this.actions.add(createExcludeMinorVersionBump());
return this;
}
public Action<ComponentSelectionWithCurrent> createExcludeMinorVersionBump() {
return (selection) -> {
String currentVersion = selection.getCurrentVersion();
int majorSeparator = currentVersion.indexOf(".");
int separator = currentVersion.indexOf(".", majorSeparator + 1);
String majorMinor = separator > 0 ? currentVersion.substring(0, separator) : currentVersion;
String candidateVersion = selection.getCandidate().getVersion();
if (!candidateVersion.startsWith(majorMinor)) {
selection.reject("Cannot upgrade to new Minor Version");
}
};
}
public DependencyExcludes releaseCandidatesVersions() {
this.actions.add(excludeVersionWithRegex("(?i).*?rc.*", "a release candidate version"));
return this;
}
public DependencyExcludes milestoneVersions() {
this.actions.add(excludeVersionWithRegex("(?i).*?m\\d+.*", "a milestone version"));
return this;
}
public DependencyExcludes snapshotVersions() {
this.actions.add(excludeVersionWithRegex(".*?-SNAPSHOT.*", "a SNAPSHOT version"));
return this;
}
public DependencyExcludes addRule(Action<ComponentSelectionRulesWithCurrent> rule) {
this.components.add(rule);
return this;
}
private Action<ComponentSelectionWithCurrent> excludeVersionWithRegex(String regex, String reason) {
Pattern pattern = Pattern.compile(regex);
return (selection) -> {
String candidateVersion = selection.getCandidate().getVersion();
if (pattern.matcher(candidateVersion).matches()) {
selection.reject(candidateVersion + " is not allowed because it is " + reason);
}
};
}
}
}
@@ -1,244 +0,0 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.convention.versions;
import com.github.benmanes.gradle.versions.reporter.result.Dependency;
import com.github.benmanes.gradle.versions.reporter.result.DependencyOutdated;
import com.github.benmanes.gradle.versions.reporter.result.Result;
import com.github.benmanes.gradle.versions.reporter.result.VersionAvailable;
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask;
import com.github.benmanes.gradle.versions.updates.gradle.GradleUpdateResult;
import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ComponentSelectionRulesWithCurrent;
import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ComponentSelectionWithCurrent;
import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ResolutionStrategyWithCurrent;
import groovy.lang.Closure;
import org.gradle.api.Action;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import reactor.core.publisher.Mono;
import java.io.File;
import java.time.Duration;
import java.util.*;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.springframework.security.convention.versions.TransitiveDependencyLookupUtils.NIMBUS_JOSE_JWT_NAME;
import static org.springframework.security.convention.versions.TransitiveDependencyLookupUtils.OIDC_SDK_NAME;
public class UpdateDependenciesPlugin implements Plugin<Project> {
private GitHubApi gitHubApi;
@Override
public void apply(Project project) {
UpdateDependenciesExtension updateDependenciesSettings = project.getExtensions().create("updateDependenciesSettings", UpdateDependenciesExtension.class, defaultFiles(project));
if (project.hasProperty("updateMode")) {
String updateMode = String.valueOf(project.findProperty("updateMode"));
updateDependenciesSettings.setUpdateMode(UpdateDependenciesExtension.UpdateMode.valueOf(updateMode));
}
if (project.hasProperty("nextVersion")) {
String nextVersion = String.valueOf(project.findProperty("nextVersion"));
updateDependenciesSettings.getGitHub().setMilestone(nextVersion);
}
if (project.hasProperty("gitHubAccessToken")) {
String gitHubAccessToken = String.valueOf(project.findProperty("gitHubAccessToken"));
updateDependenciesSettings.getGitHub().setAccessToken(gitHubAccessToken);
}
project.getTasks().register("updateDependencies", DependencyUpdatesTask.class, new Action<DependencyUpdatesTask>() {
@Override
public void execute(DependencyUpdatesTask updateDependencies) {
updateDependencies.setDescription("Update the dependencies");
updateDependencies.setCheckConstraints(true);
updateDependencies.setOutputFormatter(new Closure<Void>(null) {
@Override
public Void call(Object argument) {
Result result = (Result) argument;
if (gitHubApi == null && updateDependenciesSettings.getUpdateMode() != UpdateDependenciesExtension.UpdateMode.COMMIT) {
gitHubApi = new GitHubApi(updateDependenciesSettings.getGitHub().getAccessToken());
}
updateDependencies(result, project, updateDependenciesSettings);
updateGradleVersion(result, project, updateDependenciesSettings);
return null;
}
});
updateDependencies.resolutionStrategy(new Action<ResolutionStrategyWithCurrent>() {
@Override
public void execute(ResolutionStrategyWithCurrent resolution) {
resolution.componentSelection(new Action<ComponentSelectionRulesWithCurrent>() {
@Override
public void execute(ComponentSelectionRulesWithCurrent components) {
updateDependenciesSettings.getExcludes().getActions().forEach((action) -> {
components.all(action);
});
updateDependenciesSettings.getExcludes().getComponents().forEach((action) -> {
action.execute(components);
});
components.all((selection) -> {
ModuleComponentIdentifier candidate = selection.getCandidate();
if (candidate.getGroup().startsWith("org.apache.directory.") && !candidate.getVersion().equals(selection.getCurrentVersion())) {
selection.reject("org.apache.directory.* has breaking changes in newer versions");
}
});
String jaxbBetaRegex = ".*?b\\d+.*";
components.withModule("javax.xml.bind:jaxb-api", excludeWithRegex(jaxbBetaRegex, "Reject jaxb-api beta versions"));
components.withModule("com.sun.xml.bind:jaxb-impl", excludeWithRegex(jaxbBetaRegex, "Reject jaxb-api beta versions"));
components.withModule("commons-collections:commons-collections", excludeWithRegex("^\\d{3,}.*", "Reject commons-collections date based releases"));
}
});
}
});
}
});
}
private void updateDependencies(Result result, Project project, UpdateDependenciesExtension updateDependenciesSettings) {
SortedSet<DependencyOutdated> dependencies = result.getOutdated().getDependencies();
if (dependencies.isEmpty()) {
return;
}
Map<String, List<DependencyOutdated>> groups = new LinkedHashMap<>();
dependencies.forEach(outdated -> {
groups.computeIfAbsent(outdated.getGroup(), (key) -> new ArrayList<>()).add(outdated);
});
List<DependencyOutdated> nimbusds = groups.getOrDefault("com.nimbusds", new ArrayList<>());
DependencyOutdated oidcSdc = nimbusds.stream().filter(d -> d.getName().equals(OIDC_SDK_NAME)).findFirst().orElseGet(() -> null);
if(oidcSdc != null) {
String oidcVersion = updatedVersion(oidcSdc);
String jwtVersion = TransitiveDependencyLookupUtils.lookupJwtVersion(oidcVersion);
Dependency nimbusJoseJwtDependency = result.getCurrent().getDependencies().stream().filter(d -> d.getName().equals(NIMBUS_JOSE_JWT_NAME)).findFirst().get();
DependencyOutdated outdatedJwt = new DependencyOutdated();
outdatedJwt.setVersion(nimbusJoseJwtDependency.getVersion());
outdatedJwt.setGroup(oidcSdc.getGroup());
outdatedJwt.setName(NIMBUS_JOSE_JWT_NAME);
VersionAvailable available = new VersionAvailable();
available.setRelease(jwtVersion);
outdatedJwt.setAvailable(available);
nimbusds.add(outdatedJwt);
}
File gradlePropertiesFile = project.getRootProject().file(Project.GRADLE_PROPERTIES);
Mono<GitHubApi.FindCreateIssueResult> createIssueResult = createIssueResultMono(updateDependenciesSettings);
List<File> filesWithDependencies = updateDependenciesSettings.getFiles().get();
groups.forEach((group, outdated) -> {
outdated.forEach((dependency) -> {
String ga = dependency.getGroup() + ":" + dependency.getName() + ":";
String originalDependency = ga + dependency.getVersion();
String replacementDependency = ga + updatedVersion(dependency);
System.out.println("Update " + originalDependency + " to " + replacementDependency);
filesWithDependencies.forEach((fileWithDependency) -> {
updateDependencyInlineVersion(fileWithDependency, dependency);
updateDependencyWithVersionVariable(fileWithDependency, gradlePropertiesFile, dependency);
});
});
// commit
DependencyOutdated firstDependency = outdated.get(0);
String updatedVersion = updatedVersion(firstDependency);
String title = outdated.size() == 1 ? "Update " + firstDependency.getName() + " to " + updatedVersion : "Update " + firstDependency.getGroup() + " to " + updatedVersion;
afterGroup(updateDependenciesSettings, project.getRootDir(), title, createIssueResult);
});
}
private void afterGroup(UpdateDependenciesExtension updateDependenciesExtension, File rootDir, String title, Mono<GitHubApi.FindCreateIssueResult> createIssueResultMono) {
String commitMessage = title;
if (updateDependenciesExtension.getUpdateMode() == UpdateDependenciesExtension.UpdateMode.GITHUB_ISSUE) {
GitHubApi.FindCreateIssueResult createIssueResult = createIssueResultMono.block();
Integer issueNumber = gitHubApi.createIssue(createIssueResult.getRepositoryId(), title, createIssueResult.getLabelIds(), createIssueResult.getMilestoneId(), createIssueResult.getAssigneeId()).delayElement(Duration.ofSeconds(1)).block();
commitMessage += "\n\nCloses gh-" + issueNumber;
}
CommandLineUtils.runCommand(rootDir, "git", "commit", "-am", commitMessage);
}
private Mono<GitHubApi.FindCreateIssueResult> createIssueResultMono(UpdateDependenciesExtension updateDependenciesExtension) {
return Mono.defer(() -> {
UpdateDependenciesExtension.GitHub gitHub = updateDependenciesExtension.getGitHub();
return gitHubApi.findCreateIssueInput(gitHub.getOrganization(), gitHub.getRepository(), gitHub.getMilestone()).cache();
});
}
private void updateGradleVersion(Result result, Project project, UpdateDependenciesExtension updateDependenciesSettings) {
if (!result.getGradle().isEnabled()) {
return;
}
GradleUpdateResult current = result.getGradle().getCurrent();
GradleUpdateResult running = result.getGradle().getRunning();
if (current.compareTo(running) > 0) {
String title = "Update Gradle to " + current.getVersion();
System.out.println(title);
CommandLineUtils.runCommand(project.getRootDir(), "./gradlew", "wrapper", "--gradle-version", current.getVersion(), "--no-daemon");
afterGroup(updateDependenciesSettings, project.getRootDir(), title, createIssueResultMono(updateDependenciesSettings));
}
}
private static Supplier<List<File>> defaultFiles(Project project) {
return () -> {
List<File> result = new ArrayList<>();
result.add(project.getBuildFile());
project.getChildProjects().values().forEach((childProject) ->
result.add(childProject.getBuildFile())
);
result.add(project.getRootProject().file("buildSrc/build.gradle"));
return result;
};
}
static Action<ComponentSelectionWithCurrent> excludeWithRegex(String regex, String reason) {
Pattern pattern = Pattern.compile(regex);
return (selection) -> {
String candidateVersion = selection.getCandidate().getVersion();
if (pattern.matcher(candidateVersion).matches()) {
selection.reject(candidateVersion + " is not allowed because it is " + reason);
}
};
}
static void updateDependencyInlineVersion(File buildFile, DependencyOutdated dependency){
String ga = dependency.getGroup() + ":" + dependency.getName() + ":";
String originalDependency = ga + dependency.getVersion();
String replacementDependency = ga + updatedVersion(dependency);
FileUtils.replaceFileText(buildFile, buildFileText -> buildFileText.replace(originalDependency, replacementDependency));
}
static void updateDependencyWithVersionVariable(File scanFile, File gradlePropertiesFile, DependencyOutdated dependency) {
if (!gradlePropertiesFile.exists()) {
return;
}
FileUtils.replaceFileText(gradlePropertiesFile, (gradlePropertiesText) -> {
String ga = dependency.getGroup() + ":" + dependency.getName() + ":";
Pattern pattern = Pattern.compile("\"" + ga + "\\$\\{?([^'\"]+?)\\}?\"");
String buildFileText = FileUtils.readString(scanFile);
Matcher matcher = pattern.matcher(buildFileText);
while (matcher.find()) {
String versionVariable = matcher.group(1);
gradlePropertiesText = gradlePropertiesText.replace(versionVariable + "=" + dependency.getVersion(), versionVariable + "=" + updatedVersion(dependency));
}
return gradlePropertiesText;
});
}
private static String updatedVersion(DependencyOutdated dependency) {
VersionAvailable available = dependency.getAvailable();
String release = available.getRelease();
if (release != null) {
return release;
}
return available.getMilestone();
}
}
@@ -0,0 +1,70 @@
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.convention.versions;
import org.gradle.api.DefaultTask;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.MinimalExternalModuleDependency;
import org.gradle.api.artifacts.VersionCatalog;
import org.gradle.api.artifacts.VersionCatalogsExtension;
import org.gradle.api.plugins.JavaBasePlugin;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.TaskProvider;
public class VerifyDependenciesVersionsPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
TaskProvider<VerifyDependenciesVersionsTask> verifyDependenciesVersionsTaskProvider = project.getTasks().register("verifyDependenciesVersions", VerifyDependenciesVersionsTask.class, (task) -> {
task.setGroup("Verification");
task.setDescription("Verify that specific dependencies are using the same version");
VersionCatalog versionCatalog = project.getExtensions().getByType(VersionCatalogsExtension.class).named("libs");
MinimalExternalModuleDependency oauth2OidcSdk = versionCatalog.findLibrary("com-nimbusds-oauth2-oidc-sdk").get().get();
MinimalExternalModuleDependency nimbusJoseJwt = versionCatalog.findLibrary("com-nimbusds-nimbus-jose-jwt").get().get();
task.setOauth2OidcSdkVersion(oauth2OidcSdk.getVersionConstraint().getDisplayName());
task.setExpectedNimbusJoseJwtVersion(nimbusJoseJwt.getVersionConstraint().getDisplayName());
});
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> checkTask.dependsOn(verifyDependenciesVersionsTaskProvider));
}
public static class VerifyDependenciesVersionsTask extends DefaultTask {
private String oauth2OidcSdkVersion;
private String expectedNimbusJoseJwtVersion;
public void setOauth2OidcSdkVersion(String oauth2OidcSdkVersion) {
this.oauth2OidcSdkVersion = oauth2OidcSdkVersion;
}
public void setExpectedNimbusJoseJwtVersion(String expectedNimbusJoseJwtVersion) {
this.expectedNimbusJoseJwtVersion = expectedNimbusJoseJwtVersion;
}
@TaskAction
public void verify() {
String transitiveNimbusJoseJwtVersion = TransitiveDependencyLookupUtils.lookupJwtVersion(this.oauth2OidcSdkVersion);
if (!transitiveNimbusJoseJwtVersion.equals(this.expectedNimbusJoseJwtVersion)) {
String message = String.format("Found transitive nimbus-jose-jwt:%s in oauth2-oidc-sdk:%s, but the project contains a different version of nimbus-jose-jwt [%s]. Please align the versions.", transitiveNimbusJoseJwtVersion, this.oauth2OidcSdkVersion, this.expectedNimbusJoseJwtVersion);
throw new IllegalStateException(message);
}
}
}
}
+11 -46
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,6 @@
package s101;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -34,18 +33,11 @@ import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
@@ -71,6 +63,10 @@ public class S101Configurer {
private final Path licenseDirectory;
private final String repository;
private final String version;
private final Project project;
private final Logger logger;
@@ -90,6 +86,9 @@ public class S101Configurer {
throw new UncheckedIOException(ex);
}
this.licenseDirectory = new File(System.getProperty("user.home") + "/.Structure101/java").toPath();
S101PluginExtension extension = project.getExtensions().getByType(S101PluginExtension.class);
this.repository = extension.getRepository().get();
this.version = extension.getVersion().get();
}
public void license(String licenseId) {
@@ -129,25 +128,7 @@ public class S101Configurer {
public void configure(File installationDirectory, File configurationDirectory) {
deleteDirectory(configurationDirectory);
String version = computeVersionFromInstallation(installationDirectory);
configureProject(version, configurationDirectory);
}
private String computeVersionFromInstallation(File installationDirectory) {
File buildJar = new File(installationDirectory, "structure101-java-build.jar");
try (JarInputStream input = new JarInputStream(new FileInputStream(buildJar))) {
JarEntry entry;
while ((entry = input.getNextJarEntry()) != null) {
if (entry.getName().contains("structure101-build.properties")) {
Properties properties = new Properties();
properties.load(input);
return properties.getProperty("s101-build");
}
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
throw new IllegalStateException("Unable to determine Structure101 version");
configureProject(this.version, configurationDirectory);
}
private boolean deleteDirectory(File directoryToBeDeleted) {
@@ -161,24 +142,8 @@ public class S101Configurer {
}
private String installBuildTool(File installationDirectory, File configurationDirectory) {
String source = "https://structure101.com/binaries/v6";
try (final WebClient webClient = new WebClient()) {
HtmlPage page = webClient.getPage(source);
Matcher matcher = null;
for (HtmlAnchor anchor : page.getAnchors()) {
Matcher candidate = Pattern.compile("(structure101-build-java-all-)(.*).zip").matcher(anchor.getHrefAttribute());
if (candidate.find()) {
matcher = candidate;
}
}
if (matcher == null) {
return null;
}
copyZipToFilesystem(source, installationDirectory, matcher.group(1) + matcher.group(2));
return matcher.group(2);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
copyZipToFilesystem(this.repository, installationDirectory, "structure101-build-java-all-" + this.version);
return this.version;
}
private void copyZipToFilesystem(String source, File destination, String name) {
+1 -1
View File
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author 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-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,7 +17,12 @@
package s101;
import java.io.File;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.gradle.api.Project;
import org.gradle.api.provider.Property;
import org.gradle.api.tasks.Input;
@@ -25,6 +30,11 @@ import org.gradle.api.tasks.InputDirectory;
public class S101PluginExtension {
private final Property<String> licenseId;
private final Property<String> repository;
private final Property<String> version;
private final Property<File> installationDirectory;
private final Property<File> configurationDirectory;
private final Property<String> label;
@@ -65,6 +75,24 @@ public class S101PluginExtension {
this.label.set(label);
}
@Input
public Property<String> getRepository() {
return repository;
}
public void setRepository(String repository) {
this.repository.set(repository);
}
@Input
public Property<String> getVersion() {
return this.version;
}
public void setVersion(String version) {
this.version.set(version);
}
public S101PluginExtension(Project project) {
this.licenseId = project.getObjects().property(String.class);
if (project.hasProperty("s101.licenseId")) {
@@ -78,5 +106,31 @@ public class S101PluginExtension {
if (project.hasProperty("s101.label")) {
setLabel((String) project.findProperty("s101.label"));
}
this.repository = project.getObjects().property(String.class);
if (project.hasProperty("s101.repository")) {
setRepository((String) project.findProperty("s101.repository"));
} else {
setRepository("https://structure101.com/binaries/v6");
}
this.version = project.getObjects().property(String.class);
if (project.hasProperty("s101.version")) {
setVersion((String) project.findProperty("s101.version"));
} else {
try (final WebClient webClient = new WebClient()) {
HtmlPage page = webClient.getPage(getRepository().get());
Matcher matcher = null;
for (HtmlAnchor anchor : page.getAnchors()) {
Matcher candidate = Pattern.compile("(structure101-build-java-all-)(.*).zip").matcher(anchor.getHrefAttribute());
if (candidate.find()) {
matcher = candidate;
}
}
if (matcher != null) {
setVersion(matcher.group(2));
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
}
@@ -1 +0,0 @@
implementation-class=io.spring.gradle.convention.EclipsePlugin
@@ -5,7 +5,6 @@ import org.apache.commons.io.FileUtils;
import org.gradle.testkit.runner.BuildResult;
import org.gradle.testkit.runner.TaskOutcome;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -29,7 +28,7 @@ public class JavadocApiPluginITest {
.build();
assertThat(result.task(":api").getOutcome()).isEqualTo(TaskOutcome.SUCCESS);
File allClasses = new File(testKit.getRootDir(), "build/api/allclasses-noframe.html");
File index = new File(testKit.getRootDir(), "build/api/allclasses-index.html");
File index = new File(testKit.getRootDir(), "build/api/allclasses.html");
File listing = allClasses.exists() ? allClasses : index;
String listingText = FileUtils.readFileToString(listing);
assertThat(listingText).contains("sample/Api.html");
@@ -3,7 +3,6 @@ package org.springframework.gradle.github.milestones;
import java.nio.charset.Charset;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.concurrent.TimeUnit;
@@ -1,86 +0,0 @@
/*
* Copyright 2019-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.convention.versions;
import com.github.benmanes.gradle.versions.updates.resolutionstrategy.ComponentSelectionWithCurrent;
import org.gradle.api.Action;
import org.gradle.api.artifacts.ComponentSelection;
import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.*;
public class DependencyExcludesTests {
@Test
public void createExcludeMinorVersionBumpWhenMajorVersionBumpThenReject() {
ComponentSelection componentSelection = executeCreateExcludeMinorVersionBump("1.0.0", "2.0.0");
verify(componentSelection).reject(any());
}
@Test
public void createExcludeMinorVersionBumpWhenMajorCalVersionBumpThenReject() {
ComponentSelection componentSelection = executeCreateExcludeMinorVersionBump("2000.0.0", "2001.0.0");
verify(componentSelection).reject(any());
}
@Test
public void createExcludeMinorVersionBumpWhenMinorVersionBumpThenReject() {
ComponentSelection componentSelection = executeCreateExcludeMinorVersionBump("1.0.0", "1.1.0");
verify(componentSelection).reject(any());
}
@Test
public void createExcludeMinorVersionBumpWhenMinorCalVersionBumpThenReject() {
ComponentSelection componentSelection = executeCreateExcludeMinorVersionBump("2000.0.0", "2000.1.0");
verify(componentSelection).reject(any());
}
@Test
public void createExcludeMinorVersionBumpWhenMinorAndPatchVersionBumpThenReject() {
ComponentSelection componentSelection = executeCreateExcludeMinorVersionBump("1.0.0", "1.1.1");
verify(componentSelection).reject(any());
}
@Test
public void createExcludeMinorVersionBumpWhenPatchVersionBumpThenDoesNotReject() {
ComponentSelection componentSelection = executeCreateExcludeMinorVersionBump("1.0.0", "1.0.1");
verify(componentSelection, times(0)).reject(any());
}
private ComponentSelection executeCreateExcludeMinorVersionBump(String currentVersion, String candidateVersion) {
ComponentSelection componentSelection = mock(ComponentSelection.class);
UpdateDependenciesExtension.DependencyExcludes excludes = new UpdateDependenciesExtension(() -> Collections.emptyList()).new DependencyExcludes();
Action<ComponentSelectionWithCurrent> excludeMinorVersionBump = excludes.createExcludeMinorVersionBump();
ComponentSelectionWithCurrent selection = currentVersionAndCandidateVersion(componentSelection, currentVersion, candidateVersion);
excludeMinorVersionBump.execute(selection);
return componentSelection;
}
private ComponentSelectionWithCurrent currentVersionAndCandidateVersion(ComponentSelection componentSelection, String currentVersion, String candidateVersion) {
ModuleComponentIdentifier candidate = mock(ModuleComponentIdentifier.class);
given(componentSelection.getCandidate()).willReturn(candidate);
ComponentSelectionWithCurrent selection = new ComponentSelectionWithCurrent(currentVersion, componentSelection);
given(candidate.getVersion()).willReturn(candidateVersion);
given(componentSelection.getCandidate()).willReturn(candidate);
return selection;
}
}
@@ -9,6 +9,6 @@ repositories {
}
dependencies {
optional 'jakarta.servlet:jakarta.servlet-api:5.0.0'
optional 'jakarta.servlet:jakarta.servlet-api:3.1.0'
testCompile 'junit:junit:4.12'
}
@@ -1,7 +1,7 @@
package sample;
import org.junit.Test;
import jakarta.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequest;
public class TheTest {
@Test
+29
View File
@@ -0,0 +1,29 @@
apply plugin: 'io.spring.convention.spring-module'
dependencies {
management platform(project(":spring-security-dependencies"))
api project(':spring-security-core')
api project(':spring-security-web')
api('org.jasig.cas.client:cas-client-core') {
exclude group: 'org.glassfish.jaxb', module: 'jaxb-core'
exclude group: 'javax.xml.bind', module: 'jaxb-api'
}
api 'org.springframework:spring-beans'
api 'org.springframework:spring-context'
api 'org.springframework:spring-core'
api 'org.springframework:spring-web'
optional 'com.fasterxml.jackson.core:jackson-databind'
optional 'net.sf.ehcache:ehcache'
provided 'jakarta.servlet:jakarta.servlet-api'
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-params"
testImplementation "org.junit.jupiter:junit-jupiter-engine"
testImplementation "org.mockito:mockito-core"
testImplementation "org.mockito:mockito-junit-jupiter"
testImplementation "org.springframework:spring-test"
testImplementation 'org.skyscreamer:jsonassert'
}
@@ -0,0 +1,37 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas;
/**
* Sets the appropriate parameters for CAS's implementation of SAML (which is not
* guaranteed to be actually SAML compliant).
*
* @author Scott Battaglia
* @since 3.0
*/
public final class SamlServiceProperties extends ServiceProperties {
public static final String DEFAULT_SAML_ARTIFACT_PARAMETER = "SAMLart";
public static final String DEFAULT_SAML_SERVICE_PARAMETER = "TARGET";
public SamlServiceProperties() {
super.setArtifactParameter(DEFAULT_SAML_ARTIFACT_PARAMETER);
super.setServiceParameter(DEFAULT_SAML_SERVICE_PARAMETER);
}
}
@@ -0,0 +1,132 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
/**
* Stores properties related to this CAS service.
* <p>
* Each web application capable of processing CAS tickets is known as a service. This
* class stores the properties that are relevant to the local CAS service, being the
* application that is being secured by Spring Security.
*
* @author Ben Alex
*/
public class ServiceProperties implements InitializingBean {
public static final String DEFAULT_CAS_ARTIFACT_PARAMETER = "ticket";
public static final String DEFAULT_CAS_SERVICE_PARAMETER = "service";
private String service;
private boolean authenticateAllArtifacts;
private boolean sendRenew = false;
private String artifactParameter = DEFAULT_CAS_ARTIFACT_PARAMETER;
private String serviceParameter = DEFAULT_CAS_SERVICE_PARAMETER;
@Override
public void afterPropertiesSet() {
Assert.hasLength(this.service, "service cannot be empty.");
Assert.hasLength(this.artifactParameter, "artifactParameter cannot be empty.");
Assert.hasLength(this.serviceParameter, "serviceParameter cannot be empty.");
}
/**
* Represents the service the user is authenticating to.
* <p>
* This service is the callback URL belonging to the local Spring Security System for
* Spring secured application. For example,
*
* <pre>
* https://www.mycompany.com/application/login/cas
* </pre>
* @return the URL of the service the user is authenticating to
*/
public final String getService() {
return this.service;
}
/**
* Indicates whether the <code>renew</code> parameter should be sent to the CAS login
* URL and CAS validation URL.
* <p>
* If <code>true</code>, it will force CAS to authenticate the user again (even if the
* user has previously authenticated). During ticket validation it will require the
* ticket was generated as a consequence of an explicit login. High security
* applications would probably set this to <code>true</code>. Defaults to
* <code>false</code>, providing automated single sign on.
* @return whether to send the <code>renew</code> parameter to CAS
*/
public final boolean isSendRenew() {
return this.sendRenew;
}
public final void setSendRenew(final boolean sendRenew) {
this.sendRenew = sendRenew;
}
public final void setService(final String service) {
this.service = service;
}
public final String getArtifactParameter() {
return this.artifactParameter;
}
/**
* Configures the Request Parameter to look for when attempting to see if a CAS ticket
* was sent from the server.
* @param artifactParameter the id to use. Default is "ticket".
*/
public final void setArtifactParameter(final String artifactParameter) {
this.artifactParameter = artifactParameter;
}
/**
* Configures the Request parameter to look for when attempting to send a request to
* CAS.
* @return the service parameter to use. Default is "service".
*/
public final String getServiceParameter() {
return this.serviceParameter;
}
public final void setServiceParameter(final String serviceParameter) {
this.serviceParameter = serviceParameter;
}
public final boolean isAuthenticateAllArtifacts() {
return this.authenticateAllArtifacts;
}
/**
* If true, then any non-null artifact (ticket) should be authenticated. Additionally,
* the service will be determined dynamically in order to ensure the service matches
* the expected value for this artifact.
* @param authenticateAllArtifacts
*/
public final void setAuthenticateAllArtifacts(final boolean authenticateAllArtifacts) {
this.authenticateAllArtifacts = authenticateAllArtifacts;
}
}
@@ -0,0 +1,60 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.authentication;
import java.util.ArrayList;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.SpringSecurityCoreVersion;
/**
* Temporary authentication object needed to load the user details service.
*
* @author Scott Battaglia
* @since 3.0
*/
public final class CasAssertionAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Assertion assertion;
private final String ticket;
public CasAssertionAuthenticationToken(final Assertion assertion, final String ticket) {
super(new ArrayList<>());
this.assertion = assertion;
this.ticket = ticket;
}
@Override
public Object getPrincipal() {
return this.assertion.getPrincipal().getName();
}
@Override
public Object getCredentials() {
return this.ticket;
}
public Assertion getAssertion() {
return this.assertion;
}
}
@@ -0,0 +1,244 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.authentication;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.TicketValidationException;
import org.jasig.cas.client.validation.TicketValidator;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.util.Assert;
/**
* An {@link AuthenticationProvider} implementation that integrates with JA-SIG Central
* Authentication Service (CAS).
* <p>
* This <code>AuthenticationProvider</code> is capable of validating
* {@link UsernamePasswordAuthenticationToken} requests which contain a
* <code>principal</code> name equal to either
* {@link CasAuthenticationFilter#CAS_STATEFUL_IDENTIFIER} or
* {@link CasAuthenticationFilter#CAS_STATELESS_IDENTIFIER}. It can also validate a
* previously created {@link CasAuthenticationToken}.
*
* @author Ben Alex
* @author Scott Battaglia
*/
public class CasAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
private static final Log logger = LogFactory.getLog(CasAuthenticationProvider.class);
private AuthenticationUserDetailsService<CasAssertionAuthenticationToken> authenticationUserDetailsService;
private final UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private StatelessTicketCache statelessTicketCache = new NullStatelessTicketCache();
private String key;
private TicketValidator ticketValidator;
private ServiceProperties serviceProperties;
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
@Override
public void afterPropertiesSet() {
Assert.notNull(this.authenticationUserDetailsService, "An authenticationUserDetailsService must be set");
Assert.notNull(this.ticketValidator, "A ticketValidator must be set");
Assert.notNull(this.statelessTicketCache, "A statelessTicketCache must be set");
Assert.hasText(this.key,
"A Key is required so CasAuthenticationProvider can identify tokens it previously authenticated");
Assert.notNull(this.messages, "A message source must be set");
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!supports(authentication.getClass())) {
return null;
}
if (authentication instanceof UsernamePasswordAuthenticationToken
&& (!CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER.equals(authentication.getPrincipal().toString())
&& !CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER
.equals(authentication.getPrincipal().toString()))) {
// UsernamePasswordAuthenticationToken not CAS related
return null;
}
// If an existing CasAuthenticationToken, just check we created it
if (authentication instanceof CasAuthenticationToken) {
if (this.key.hashCode() != ((CasAuthenticationToken) authentication).getKeyHash()) {
throw new BadCredentialsException(this.messages.getMessage("CasAuthenticationProvider.incorrectKey",
"The presented CasAuthenticationToken does not contain the expected key"));
}
return authentication;
}
// Ensure credentials are presented
if ((authentication.getCredentials() == null) || "".equals(authentication.getCredentials())) {
throw new BadCredentialsException(this.messages.getMessage("CasAuthenticationProvider.noServiceTicket",
"Failed to provide a CAS service ticket to validate"));
}
boolean stateless = (authentication instanceof UsernamePasswordAuthenticationToken
&& CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER.equals(authentication.getPrincipal()));
CasAuthenticationToken result = null;
if (stateless) {
// Try to obtain from cache
result = this.statelessTicketCache.getByTicketId(authentication.getCredentials().toString());
}
if (result == null) {
result = this.authenticateNow(authentication);
result.setDetails(authentication.getDetails());
}
if (stateless) {
// Add to cache
this.statelessTicketCache.putTicketInCache(result);
}
return result;
}
private CasAuthenticationToken authenticateNow(final Authentication authentication) throws AuthenticationException {
try {
Assertion assertion = this.ticketValidator.validate(authentication.getCredentials().toString(),
getServiceUrl(authentication));
UserDetails userDetails = loadUserByAssertion(assertion);
this.userDetailsChecker.check(userDetails);
return new CasAuthenticationToken(this.key, userDetails, authentication.getCredentials(),
this.authoritiesMapper.mapAuthorities(userDetails.getAuthorities()), userDetails, assertion);
}
catch (TicketValidationException ex) {
throw new BadCredentialsException(ex.getMessage(), ex);
}
}
/**
* Gets the serviceUrl. If the {@link Authentication#getDetails()} is an instance of
* {@link ServiceAuthenticationDetails}, then
* {@link ServiceAuthenticationDetails#getServiceUrl()} is used. Otherwise, the
* {@link ServiceProperties#getService()} is used.
* @param authentication
* @return
*/
private String getServiceUrl(Authentication authentication) {
String serviceUrl;
if (authentication.getDetails() instanceof ServiceAuthenticationDetails) {
return ((ServiceAuthenticationDetails) authentication.getDetails()).getServiceUrl();
}
Assert.state(this.serviceProperties != null,
"serviceProperties cannot be null unless Authentication.getDetails() implements ServiceAuthenticationDetails.");
Assert.state(this.serviceProperties.getService() != null,
"serviceProperties.getService() cannot be null unless Authentication.getDetails() implements ServiceAuthenticationDetails.");
serviceUrl = this.serviceProperties.getService();
logger.debug(LogMessage.format("serviceUrl = %s", serviceUrl));
return serviceUrl;
}
/**
* Template method for retrieving the UserDetails based on the assertion. Default is
* to call configured userDetailsService and pass the username. Deployers can override
* this method and retrieve the user based on any criteria they desire.
* @param assertion The CAS Assertion.
* @return the UserDetails.
*/
protected UserDetails loadUserByAssertion(final Assertion assertion) {
final CasAssertionAuthenticationToken token = new CasAssertionAuthenticationToken(assertion, "");
return this.authenticationUserDetailsService.loadUserDetails(token);
}
@SuppressWarnings("unchecked")
/**
* Sets the UserDetailsService to use. This is a convenience method to invoke
*/
public void setUserDetailsService(final UserDetailsService userDetailsService) {
this.authenticationUserDetailsService = new UserDetailsByNameServiceWrapper(userDetailsService);
}
public void setAuthenticationUserDetailsService(
final AuthenticationUserDetailsService<CasAssertionAuthenticationToken> authenticationUserDetailsService) {
this.authenticationUserDetailsService = authenticationUserDetailsService;
}
public void setServiceProperties(final ServiceProperties serviceProperties) {
this.serviceProperties = serviceProperties;
}
protected String getKey() {
return this.key;
}
public void setKey(String key) {
this.key = key;
}
public StatelessTicketCache getStatelessTicketCache() {
return this.statelessTicketCache;
}
protected TicketValidator getTicketValidator() {
return this.ticketValidator;
}
@Override
public void setMessageSource(final MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
public void setStatelessTicketCache(final StatelessTicketCache statelessTicketCache) {
this.statelessTicketCache = statelessTicketCache;
}
public void setTicketValidator(final TicketValidator ticketValidator) {
this.ticketValidator = ticketValidator;
}
public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) {
this.authoritiesMapper = authoritiesMapper;
}
@Override
public boolean supports(final Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication))
|| (CasAuthenticationToken.class.isAssignableFrom(authentication))
|| (CasAssertionAuthenticationToken.class.isAssignableFrom(authentication));
}
}
@@ -0,0 +1,173 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.authentication;
import java.io.Serializable;
import java.util.Collection;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityCoreVersion;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
/**
* Represents a successful CAS <code>Authentication</code>.
*
* @author Ben Alex
* @author Scott Battaglia
*/
public class CasAuthenticationToken extends AbstractAuthenticationToken implements Serializable {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
private final Object credentials;
private final Object principal;
private final UserDetails userDetails;
private final int keyHash;
private final Assertion assertion;
/**
* Constructor.
* @param key to identify if this object made by a given
* {@link CasAuthenticationProvider}
* @param principal typically the UserDetails object (cannot be <code>null</code>)
* @param credentials the service/proxy ticket ID from CAS (cannot be
* <code>null</code>)
* @param authorities the authorities granted to the user (from the
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
* be <code>null</code>)
* @param userDetails the user details (from the
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
* be <code>null</code>)
* @param assertion the assertion returned from the CAS servers. It contains the
* principal and how to obtain a proxy ticket for the user.
* @throws IllegalArgumentException if a <code>null</code> was passed
*/
public CasAuthenticationToken(final String key, final Object principal, final Object credentials,
final Collection<? extends GrantedAuthority> authorities, final UserDetails userDetails,
final Assertion assertion) {
this(extractKeyHash(key), principal, credentials, authorities, userDetails, assertion);
}
/**
* Private constructor for Jackson Deserialization support
* @param keyHash hashCode of provided key to identify if this object made by a given
* {@link CasAuthenticationProvider}
* @param principal typically the UserDetails object (cannot be <code>null</code>)
* @param credentials the service/proxy ticket ID from CAS (cannot be
* <code>null</code>)
* @param authorities the authorities granted to the user (from the
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
* be <code>null</code>)
* @param userDetails the user details (from the
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
* be <code>null</code>)
* @param assertion the assertion returned from the CAS servers. It contains the
* principal and how to obtain a proxy ticket for the user.
* @throws IllegalArgumentException if a <code>null</code> was passed
* @since 4.2
*/
private CasAuthenticationToken(final Integer keyHash, final Object principal, final Object credentials,
final Collection<? extends GrantedAuthority> authorities, final UserDetails userDetails,
final Assertion assertion) {
super(authorities);
if ((principal == null) || "".equals(principal) || (credentials == null) || "".equals(credentials)
|| (authorities == null) || (userDetails == null) || (assertion == null)) {
throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
}
this.keyHash = keyHash;
this.principal = principal;
this.credentials = credentials;
this.userDetails = userDetails;
this.assertion = assertion;
setAuthenticated(true);
}
private static Integer extractKeyHash(String key) {
Assert.hasLength(key, "key cannot be null or empty");
return key.hashCode();
}
@Override
public boolean equals(final Object obj) {
if (!super.equals(obj)) {
return false;
}
if (obj instanceof CasAuthenticationToken) {
CasAuthenticationToken test = (CasAuthenticationToken) obj;
if (!this.assertion.equals(test.getAssertion())) {
return false;
}
if (this.getKeyHash() != test.getKeyHash()) {
return false;
}
return true;
}
return false;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + this.credentials.hashCode();
result = 31 * result + this.principal.hashCode();
result = 31 * result + this.userDetails.hashCode();
result = 31 * result + this.keyHash;
result = 31 * result + ObjectUtils.nullSafeHashCode(this.assertion);
return result;
}
@Override
public Object getCredentials() {
return this.credentials;
}
public int getKeyHash() {
return this.keyHash;
}
@Override
public Object getPrincipal() {
return this.principal;
}
public Assertion getAssertion() {
return this.assertion;
}
public UserDetails getUserDetails() {
return this.userDetails;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
sb.append(" Assertion: ").append(this.assertion);
sb.append(" Credentials (Service/Proxy Ticket): ").append(this.credentials);
return (sb.toString());
}
}
@@ -0,0 +1,80 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.authentication;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.log.LogMessage;
import org.springframework.util.Assert;
/**
* Caches tickets using a Spring IoC defined
* <a href="https://www.ehcache.org/">EHCACHE</a>.
*
* @author Ben Alex
* @deprecated since 5.6. In favor of JCache based implementations
*/
@Deprecated
public class EhCacheBasedTicketCache implements StatelessTicketCache, InitializingBean {
private static final Log logger = LogFactory.getLog(EhCacheBasedTicketCache.class);
private Ehcache cache;
@Override
public void afterPropertiesSet() {
Assert.notNull(this.cache, "cache mandatory");
}
@Override
public CasAuthenticationToken getByTicketId(final String serviceTicket) {
final Element element = this.cache.get(serviceTicket);
logger.debug(LogMessage.of(() -> "Cache hit: " + (element != null) + "; service ticket: " + serviceTicket));
return (element != null) ? (CasAuthenticationToken) element.getValue() : null;
}
public Ehcache getCache() {
return this.cache;
}
@Override
public void putTicketInCache(final CasAuthenticationToken token) {
final Element element = new Element(token.getCredentials().toString(), token);
logger.debug(LogMessage.of(() -> "Cache put: " + element.getKey()));
this.cache.put(element);
}
@Override
public void removeTicketFromCache(final CasAuthenticationToken token) {
logger.debug(LogMessage.of(() -> "Cache remove: " + token.getCredentials().toString()));
this.removeTicketFromCache(token.getCredentials().toString());
}
@Override
public void removeTicketFromCache(final String serviceTicket) {
this.cache.remove(serviceTicket);
}
public void setCache(final Ehcache cache) {
this.cache = cache;
}
}
@@ -0,0 +1,64 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.authentication;
/**
* Implementation of @link {@link StatelessTicketCache} that has no backing cache. Useful
* in instances where storing of tickets for stateless session management is not required.
* <p>
* This is the default StatelessTicketCache of the @link {@link CasAuthenticationProvider}
* to eliminate the unnecessary dependency on EhCache that applications have even if they
* are not using the stateless session management.
*
* @author Scott Battaglia
* @see CasAuthenticationProvider
*/
public final class NullStatelessTicketCache implements StatelessTicketCache {
/**
* @return null since we are not storing any tickets.
*/
@Override
public CasAuthenticationToken getByTicketId(final String serviceTicket) {
return null;
}
/**
* This is a no-op since we are not storing tickets.
*/
@Override
public void putTicketInCache(final CasAuthenticationToken token) {
// nothing to do
}
/**
* This is a no-op since we are not storing tickets.
*/
@Override
public void removeTicketFromCache(final CasAuthenticationToken token) {
// nothing to do
}
/**
* This is a no-op since we are not storing tickets.
*/
@Override
public void removeTicketFromCache(final String serviceTicket) {
// nothing to do
}
}
@@ -0,0 +1,69 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.authentication;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cache.Cache;
import org.springframework.core.log.LogMessage;
import org.springframework.util.Assert;
/**
* Caches tickets using a Spring IoC defined {@link Cache}.
*
* @author Marten Deinum
* @since 3.2
*
*/
public class SpringCacheBasedTicketCache implements StatelessTicketCache {
private static final Log logger = LogFactory.getLog(SpringCacheBasedTicketCache.class);
private final Cache cache;
public SpringCacheBasedTicketCache(Cache cache) {
Assert.notNull(cache, "cache mandatory");
this.cache = cache;
}
@Override
public CasAuthenticationToken getByTicketId(final String serviceTicket) {
final Cache.ValueWrapper element = (serviceTicket != null) ? this.cache.get(serviceTicket) : null;
logger.debug(LogMessage.of(() -> "Cache hit: " + (element != null) + "; service ticket: " + serviceTicket));
return (element != null) ? (CasAuthenticationToken) element.get() : null;
}
@Override
public void putTicketInCache(final CasAuthenticationToken token) {
String key = token.getCredentials().toString();
logger.debug(LogMessage.of(() -> "Cache put: " + key));
this.cache.put(key, token);
}
@Override
public void removeTicketFromCache(final CasAuthenticationToken token) {
logger.debug(LogMessage.of(() -> "Cache remove: " + token.getCredentials().toString()));
this.removeTicketFromCache(token.getCredentials().toString());
}
@Override
public void removeTicketFromCache(final String serviceTicket) {
this.cache.evict(serviceTicket);
}
}
@@ -0,0 +1,110 @@
/*
* Copyright 2004 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.authentication;
/**
* Caches CAS service tickets and CAS proxy tickets for stateless connections.
*
* <p>
* When a service ticket or proxy ticket is validated against the CAS server, it is unable
* to be used again. Most types of callers are stateful and are associated with a given
* <code>HttpSession</code>. This allows the affirmative CAS validation outcome to be
* stored in the <code>HttpSession</code>, meaning the removal of the ticket from the CAS
* server is not an issue.
* </p>
*
* <P>
* Stateless callers, such as remoting protocols, cannot take advantage of
* <code>HttpSession</code>. If the stateless caller is located a significant network
* distance from the CAS server, acquiring a fresh service ticket or proxy ticket for each
* invocation would be expensive.
* </p>
*
* <P>
* To avoid this issue with stateless callers, it is expected stateless callers will
* obtain a single service ticket or proxy ticket, and then present this same ticket to
* the Spring Security secured application on each occasion. As no
* <code>HttpSession</code> is available for such callers, the affirmative CAS validation
* outcome cannot be stored in this location.
* </p>
*
* <P>
* The <code>StatelessTicketCache</code> enables the service tickets and proxy tickets
* belonging to stateless callers to be placed in a cache. This in-memory cache stores the
* <code>CasAuthenticationToken</code>, effectively providing the same capability as a
* <code>HttpSession</code> with the ticket identifier being the key rather than a session
* identifier.
* </p>
*
* <P>
* Implementations should provide a reasonable timeout on stored entries, such that the
* stateless caller are not required to unnecessarily acquire fresh CAS service tickets or
* proxy tickets.
* </p>
*
* @author Ben Alex
*/
public interface StatelessTicketCache {
/**
* Retrieves the <code>CasAuthenticationToken</code> associated with the specified
* ticket.
*
* <P>
* If not found, returns a <code>null</code><code>CasAuthenticationToken</code>.
* </p>
* @return the fully populated authentication token
*/
CasAuthenticationToken getByTicketId(String serviceTicket);
/**
* Adds the specified <code>CasAuthenticationToken</code> to the cache.
*
* <P>
* The {@link CasAuthenticationToken#getCredentials()} method is used to retrieve the
* service ticket number.
* </p>
* @param token to be added to the cache
*/
void putTicketInCache(CasAuthenticationToken token);
/**
* Removes the specified ticket from the cache, as per
* {@link #removeTicketFromCache(String)}.
*
* <P>
* Implementations should use {@link CasAuthenticationToken#getCredentials()} to
* obtain the ticket and then delegate to the {@link #removeTicketFromCache(String)}
* method.
* </p>
* @param token to be removed
*/
void removeTicketFromCache(CasAuthenticationToken token);
/**
* Removes the specified ticket from the cache, meaning that future calls will require
* a new service ticket.
*
* <P>
* This is in case applications wish to provide a session termination capability for
* their stateless clients.
* </p>
* @param serviceTicket to be removed
*/
void removeTicketFromCache(String serviceTicket);
}
@@ -0,0 +1,21 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* An {@code AuthenticationProvider} that can process CAS service tickets and proxy
* tickets.
*/
package org.springframework.security.cas.authentication;
@@ -0,0 +1,69 @@
/*
* Copyright 2015-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.jackson2;
import java.util.Date;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.jasig.cas.client.authentication.AttributePrincipal;
/**
* Helps in jackson deserialization of class
* {@link org.jasig.cas.client.validation.AssertionImpl}, which is used with
* {@link org.springframework.security.cas.authentication.CasAuthenticationToken}. To use
* this class we need to register with
* {@link com.fasterxml.jackson.databind.ObjectMapper}. Type information will be stored
* in @class property.
* <p>
* <pre>
* ObjectMapper mapper = new ObjectMapper();
* mapper.registerModule(new CasJackson2Module());
* </pre>
*
* @author Jitendra Singh
* @since 4.2
* @see CasJackson2Module
* @see org.springframework.security.jackson2.SecurityJackson2Modules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
class AssertionImplMixin {
/**
* Mixin Constructor helps in deserialize
* {@link org.jasig.cas.client.validation.AssertionImpl}
* @param principal the Principal to associate with the Assertion.
* @param validFromDate when the assertion is valid from.
* @param validUntilDate when the assertion is valid to.
* @param authenticationDate when the assertion is authenticated.
* @param attributes the key/value pairs for this attribute.
*/
@JsonCreator
AssertionImplMixin(@JsonProperty("principal") AttributePrincipal principal,
@JsonProperty("validFromDate") Date validFromDate, @JsonProperty("validUntilDate") Date validUntilDate,
@JsonProperty("authenticationDate") Date authenticationDate,
@JsonProperty("attributes") Map<String, Object> attributes) {
}
}
@@ -0,0 +1,66 @@
/*
* Copyright 2015-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.jackson2;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.jasig.cas.client.proxy.ProxyRetriever;
/**
* Helps in deserialize {@link org.jasig.cas.client.authentication.AttributePrincipalImpl}
* which is used with
* {@link org.springframework.security.cas.authentication.CasAuthenticationToken}. Type
* information will be stored in property named @class.
* <p>
* <pre>
* ObjectMapper mapper = new ObjectMapper();
* mapper.registerModule(new CasJackson2Module());
* </pre>
*
* @author Jitendra Singh
* @since 4.2
* @see CasJackson2Module
* @see org.springframework.security.jackson2.SecurityJackson2Modules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, getterVisibility = JsonAutoDetect.Visibility.NONE,
isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
class AttributePrincipalImplMixin {
/**
* Mixin Constructor helps in deserialize
* {@link org.jasig.cas.client.authentication.AttributePrincipalImpl}
* @param name the unique identifier for the principal.
* @param attributes the key/value pairs for this principal.
* @param proxyGrantingTicket the ticket associated with this principal.
* @param proxyRetriever the ProxyRetriever implementation to call back to the CAS
* server.
*/
@JsonCreator
AttributePrincipalImplMixin(@JsonProperty("name") String name,
@JsonProperty("attributes") Map<String, Object> attributes,
@JsonProperty("proxyGrantingTicket") String proxyGrantingTicket,
@JsonProperty("proxyRetriever") ProxyRetriever proxyRetriever) {
}
}
@@ -0,0 +1,83 @@
/*
* Copyright 2015-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.jackson2;
import java.util.Collection;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.authentication.CasAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
/**
* Mixin class which helps in deserialize
* {@link org.springframework.security.cas.authentication.CasAuthenticationToken} using
* jackson. Two more dependent classes needs to register along with this mixin class.
* <ol>
* <li>{@link org.springframework.security.cas.jackson2.AssertionImplMixin}</li>
* <li>{@link org.springframework.security.cas.jackson2.AttributePrincipalImplMixin}</li>
* </ol>
*
* <p>
*
* <pre>
* ObjectMapper mapper = new ObjectMapper();
* mapper.registerModule(new CasJackson2Module());
* </pre>
*
* @author Jitendra Singh
* @since 4.2
* @see CasJackson2Module
* @see org.springframework.security.jackson2.SecurityJackson2Modules
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, isGetterVisibility = JsonAutoDetect.Visibility.NONE,
getterVisibility = JsonAutoDetect.Visibility.NONE, creatorVisibility = JsonAutoDetect.Visibility.ANY)
@JsonIgnoreProperties(ignoreUnknown = true)
class CasAuthenticationTokenMixin {
/**
* Mixin Constructor helps in deserialize {@link CasAuthenticationToken}
* @param keyHash hashCode of provided key to identify if this object made by a given
* {@link CasAuthenticationProvider}
* @param principal typically the UserDetails object (cannot be <code>null</code>)
* @param credentials the service/proxy ticket ID from CAS (cannot be
* <code>null</code>)
* @param authorities the authorities granted to the user (from the
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
* be <code>null</code>)
* @param userDetails the user details (from the
* {@link org.springframework.security.core.userdetails.UserDetailsService}) (cannot
* be <code>null</code>)
* @param assertion the assertion returned from the CAS servers. It contains the
* principal and how to obtain a proxy ticket for the user.
*/
@JsonCreator
CasAuthenticationTokenMixin(@JsonProperty("keyHash") Integer keyHash, @JsonProperty("principal") Object principal,
@JsonProperty("credentials") Object credentials,
@JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
@JsonProperty("userDetails") UserDetails userDetails, @JsonProperty("assertion") Assertion assertion) {
}
}
@@ -0,0 +1,58 @@
/*
* Copyright 2015-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.jackson2;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.module.SimpleModule;
import org.jasig.cas.client.authentication.AttributePrincipalImpl;
import org.jasig.cas.client.validation.AssertionImpl;
import org.springframework.security.cas.authentication.CasAuthenticationToken;
import org.springframework.security.jackson2.SecurityJackson2Modules;
/**
* Jackson module for spring-security-cas. This module register
* {@link AssertionImplMixin}, {@link AttributePrincipalImplMixin} and
* {@link CasAuthenticationTokenMixin}. If no default typing enabled by default then it'll
* enable it because typing info is needed to properly serialize/deserialize objects. In
* order to use this module just add this module into your ObjectMapper configuration.
*
* <pre>
* ObjectMapper mapper = new ObjectMapper();
* mapper.registerModule(new CasJackson2Module());
* </pre> <b>Note: use {@link SecurityJackson2Modules#getModules(ClassLoader)} to get list
* of all security modules on the classpath.</b>
*
* @author Jitendra Singh.
* @since 4.2
* @see org.springframework.security.jackson2.SecurityJackson2Modules
*/
public class CasJackson2Module extends SimpleModule {
public CasJackson2Module() {
super(CasJackson2Module.class.getName(), new Version(1, 0, 0, null, null, null));
}
@Override
public void setupModule(SetupContext context) {
SecurityJackson2Modules.enableDefaultTyping(context.getOwner());
context.setMixInAnnotations(AssertionImpl.class, AssertionImplMixin.class);
context.setMixInAnnotations(AttributePrincipalImpl.class, AttributePrincipalImplMixin.class);
context.setMixInAnnotations(CasAuthenticationToken.class, CasAuthenticationTokenMixin.class);
}
}
@@ -0,0 +1,21 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Spring Security support for Jasig's Central Authentication Service
* (<a href="https://www.jasig.org/cas">CAS</a>).
*/
package org.springframework.security.cas;
@@ -0,0 +1,51 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.userdetails;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
/**
* Abstract class for using the provided CAS assertion to construct a new User object.
* This generally is most useful when combined with a SAML-based response from the CAS
* Server/client.
*
* @author Scott Battaglia
* @since 3.0
*/
public abstract class AbstractCasAssertionUserDetailsService
implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken> {
@Override
public final UserDetails loadUserDetails(final CasAssertionAuthenticationToken token) {
return loadUserDetails(token.getAssertion());
}
/**
* Protected template method for construct a
* {@link org.springframework.security.core.userdetails.UserDetails} via the supplied
* CAS assertion.
* @param assertion the assertion to use to construct the new UserDetails. CANNOT be
* NULL.
* @return the newly constructed UserDetails.
*/
protected abstract UserDetails loadUserDetails(Assertion assertion);
}
@@ -0,0 +1,87 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.userdetails;
import java.util.ArrayList;
import java.util.List;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.Assert;
/**
* Populates the {@link org.springframework.security.core.GrantedAuthority}s for a user by
* reading a list of attributes that were returned as part of the CAS response. Each
* attribute is read and each value of the attribute is turned into a GrantedAuthority. If
* the attribute has no value then its not added.
*
* @author Scott Battaglia
* @since 3.0
*/
public final class GrantedAuthorityFromAssertionAttributesUserDetailsService
extends AbstractCasAssertionUserDetailsService {
private static final String NON_EXISTENT_PASSWORD_VALUE = "NO_PASSWORD";
private final String[] attributes;
private boolean convertToUpperCase = true;
public GrantedAuthorityFromAssertionAttributesUserDetailsService(final String[] attributes) {
Assert.notNull(attributes, "attributes cannot be null.");
Assert.isTrue(attributes.length > 0, "At least one attribute is required to retrieve roles from.");
this.attributes = attributes;
}
@SuppressWarnings("unchecked")
@Override
protected UserDetails loadUserDetails(final Assertion assertion) {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
for (String attribute : this.attributes) {
Object value = assertion.getPrincipal().getAttributes().get(attribute);
if (value != null) {
if (value instanceof List) {
for (Object o : (List<?>) value) {
grantedAuthorities.add(createSimpleGrantedAuthority(o));
}
}
else {
grantedAuthorities.add(createSimpleGrantedAuthority(value));
}
}
}
return new User(assertion.getPrincipal().getName(), NON_EXISTENT_PASSWORD_VALUE, true, true, true, true,
grantedAuthorities);
}
private SimpleGrantedAuthority createSimpleGrantedAuthority(Object o) {
return new SimpleGrantedAuthority(this.convertToUpperCase ? o.toString().toUpperCase() : o.toString());
}
/**
* Converts the returned attribute values to uppercase values.
* @param convertToUpperCase true if it should convert, false otherwise.
*/
public void setConvertToUpperCase(final boolean convertToUpperCase) {
this.convertToUpperCase = convertToUpperCase;
}
}
@@ -0,0 +1,150 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jasig.cas.client.util.CommonUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.util.Assert;
/**
* Used by the <code>ExceptionTranslationFilter</code> to commence authentication via the
* JA-SIG Central Authentication Service (CAS).
* <p>
* The user's browser will be redirected to the JA-SIG CAS enterprise-wide login page.
* This page is specified by the <code>loginUrl</code> property. Once login is complete,
* the CAS login page will redirect to the page indicated by the <code>service</code>
* property. The <code>service</code> is a HTTP URL belonging to the current application.
* The <code>service</code> URL is monitored by the {@link CasAuthenticationFilter}, which
* will validate the CAS login was successful.
*
* @author Ben Alex
* @author Scott Battaglia
*/
public class CasAuthenticationEntryPoint implements AuthenticationEntryPoint, InitializingBean {
private ServiceProperties serviceProperties;
private String loginUrl;
/**
* Determines whether the Service URL should include the session id for the specific
* user. As of CAS 3.0.5, the session id will automatically be stripped. However,
* older versions of CAS (i.e. CAS 2), do not automatically strip the session
* identifier (this is a bug on the part of the older server implementations), so an
* option to disable the session encoding is provided for backwards compatibility.
*
* By default, encoding is enabled.
*/
private boolean encodeServiceUrlWithSessionId = true;
@Override
public void afterPropertiesSet() {
Assert.hasLength(this.loginUrl, "loginUrl must be specified");
Assert.notNull(this.serviceProperties, "serviceProperties must be specified");
Assert.notNull(this.serviceProperties.getService(), "serviceProperties.getService() cannot be null.");
}
@Override
public final void commence(final HttpServletRequest servletRequest, HttpServletResponse response,
AuthenticationException authenticationException) throws IOException {
String urlEncodedService = createServiceUrl(servletRequest, response);
String redirectUrl = createRedirectUrl(urlEncodedService);
preCommence(servletRequest, response);
response.sendRedirect(redirectUrl);
}
/**
* Constructs a new Service Url. The default implementation relies on the CAS client
* to do the bulk of the work.
* @param request the HttpServletRequest
* @param response the HttpServlet Response
* @return the constructed service url. CANNOT be NULL.
*/
protected String createServiceUrl(HttpServletRequest request, HttpServletResponse response) {
return CommonUtils.constructServiceUrl(null, response, this.serviceProperties.getService(), null,
this.serviceProperties.getArtifactParameter(), this.encodeServiceUrlWithSessionId);
}
/**
* Constructs the Url for Redirection to the CAS server. Default implementation relies
* on the CAS client to do the bulk of the work.
* @param serviceUrl the service url that should be included.
* @return the redirect url. CANNOT be NULL.
*/
protected String createRedirectUrl(String serviceUrl) {
return CommonUtils.constructRedirectUrl(this.loginUrl, this.serviceProperties.getServiceParameter(), serviceUrl,
this.serviceProperties.isSendRenew(), false);
}
/**
* Template method for you to do your own pre-processing before the redirect occurs.
* @param request the HttpServletRequest
* @param response the HttpServletResponse
*/
protected void preCommence(HttpServletRequest request, HttpServletResponse response) {
}
/**
* The enterprise-wide CAS login URL. Usually something like
* <code>https://www.mycompany.com/cas/login</code>.
* @return the enterprise-wide CAS login URL
*/
public final String getLoginUrl() {
return this.loginUrl;
}
public final ServiceProperties getServiceProperties() {
return this.serviceProperties;
}
public final void setLoginUrl(String loginUrl) {
this.loginUrl = loginUrl;
}
public final void setServiceProperties(ServiceProperties serviceProperties) {
this.serviceProperties = serviceProperties;
}
/**
* Sets whether to encode the service url with the session id or not.
* @param encodeServiceUrlWithSessionId whether to encode the service url with the
* session id or not.
*/
public final void setEncodeServiceUrlWithSessionId(boolean encodeServiceUrlWithSessionId) {
this.encodeServiceUrlWithSessionId = encodeServiceUrlWithSessionId;
}
/**
* Sets whether to encode the service url with the session id or not.
* @return whether to encode the service url with the session id or not.
*
*/
protected boolean getEncodeServiceUrlWithSessionId() {
return this.encodeServiceUrlWithSessionId;
}
}
@@ -0,0 +1,415 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.validation.TicketValidator;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.context.NullSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
/**
* Processes a CAS service ticket, obtains proxy granting tickets, and processes proxy
* tickets.
* <h2>Service Tickets</h2>
* <p>
* A service ticket consists of an opaque ticket string. It arrives at this filter by the
* user's browser successfully authenticating using CAS, and then receiving a HTTP
* redirect to a <code>service</code>. The opaque ticket string is presented in the
* <code>ticket</code> request parameter.
* <p>
* This filter monitors the <code>service</code> URL so it can receive the service ticket
* and process it. By default this filter processes the URL <tt>/login/cas</tt>. When
* processing this URL, the value of {@link ServiceProperties#getService()} is used as the
* <tt>service</tt> when validating the <code>ticket</code>. This means that it is
* important that {@link ServiceProperties#getService()} specifies the same value as the
* <tt>filterProcessesUrl</tt>.
* <p>
* Processing the service ticket involves creating a
* <code>UsernamePasswordAuthenticationToken</code> which uses
* {@link #CAS_STATEFUL_IDENTIFIER} for the <code>principal</code> and the opaque ticket
* string as the <code>credentials</code>.
* <h2>Obtaining Proxy Granting Tickets</h2>
* <p>
* If specified, the filter can also monitor the <code>proxyReceptorUrl</code>. The filter
* will respond to requests matching this url so that the CAS Server can provide a PGT to
* the filter. Note that in addition to the <code>proxyReceptorUrl</code> a non-null
* <code>proxyGrantingTicketStorage</code> must be provided in order for the filter to
* respond to proxy receptor requests. By configuring a shared
* {@link ProxyGrantingTicketStorage} between the {@link TicketValidator} and the
* CasAuthenticationFilter one can have the CasAuthenticationFilter handle the proxying
* requirements for CAS.
* <h2>Proxy Tickets</h2>
* <p>
* The filter can process tickets present on any url. This is useful when wanting to
* process proxy tickets. In order for proxy tickets to get processed
* {@link ServiceProperties#isAuthenticateAllArtifacts()} must return <code>true</code>.
* Additionally, if the request is already authenticated, authentication will <b>not</b>
* occur. Last, {@link AuthenticationDetailsSource#buildDetails(Object)} must return a
* {@link ServiceAuthenticationDetails}. This can be accomplished using the
* {@link ServiceAuthenticationDetailsSource}. In this case
* {@link ServiceAuthenticationDetails#getServiceUrl()} will be used for the service url.
* <p>
* Processing the proxy ticket involves creating a
* <code>UsernamePasswordAuthenticationToken</code> which uses
* {@link #CAS_STATELESS_IDENTIFIER} for the <code>principal</code> and the opaque ticket
* string as the <code>credentials</code>. When a proxy ticket is successfully
* authenticated, the FilterChain continues and the
* <code>authenticationSuccessHandler</code> is not used.
* <h2>Notes about the <code>AuthenticationManager</code></h2>
* <p>
* The configured <code>AuthenticationManager</code> is expected to provide a provider
* that can recognise <code>UsernamePasswordAuthenticationToken</code>s containing this
* special <code>principal</code> name, and process them accordingly by validation with
* the CAS server. Additionally, it should be capable of using the result of
* {@link ServiceAuthenticationDetails#getServiceUrl()} as the service when validating the
* ticket.
* <h2>Example Configuration</h2>
* <p>
* An example configuration that supports service tickets, obtaining proxy granting
* tickets, and proxy tickets is illustrated below:
*
* <pre>
* &lt;b:bean id=&quot;serviceProperties&quot;
* class=&quot;org.springframework.security.cas.ServiceProperties&quot;
* p:service=&quot;https://service.example.com/cas-sample/login/cas&quot;
* p:authenticateAllArtifacts=&quot;true&quot;/&gt;
* &lt;b:bean id=&quot;casEntryPoint&quot;
* class=&quot;org.springframework.security.cas.web.CasAuthenticationEntryPoint&quot;
* p:serviceProperties-ref=&quot;serviceProperties&quot; p:loginUrl=&quot;https://login.example.org/cas/login&quot; /&gt;
* &lt;b:bean id=&quot;casFilter&quot;
* class=&quot;org.springframework.security.cas.web.CasAuthenticationFilter&quot;
* p:authenticationManager-ref=&quot;authManager&quot;
* p:serviceProperties-ref=&quot;serviceProperties&quot;
* p:proxyGrantingTicketStorage-ref=&quot;pgtStorage&quot;
* p:proxyReceptorUrl=&quot;/login/cas/proxyreceptor&quot;&gt;
* &lt;b:property name=&quot;authenticationDetailsSource&quot;&gt;
* &lt;b:bean class=&quot;org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource&quot;/&gt;
* &lt;/b:property&gt;
* &lt;b:property name=&quot;authenticationFailureHandler&quot;&gt;
* &lt;b:bean class=&quot;org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler&quot;
* p:defaultFailureUrl=&quot;/casfailed.jsp&quot;/&gt;
* &lt;/b:property&gt;
* &lt;/b:bean&gt;
* &lt;!--
* NOTE: In a real application you should not use an in memory implementation. You will also want
* to ensure to clean up expired tickets by calling ProxyGrantingTicketStorage.cleanup()
* --&gt;
* &lt;b:bean id=&quot;pgtStorage&quot; class=&quot;org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl&quot;/&gt;
* &lt;b:bean id=&quot;casAuthProvider&quot; class=&quot;org.springframework.security.cas.authentication.CasAuthenticationProvider&quot;
* p:serviceProperties-ref=&quot;serviceProperties&quot;
* p:key=&quot;casAuthProviderKey&quot;&gt;
* &lt;b:property name=&quot;authenticationUserDetailsService&quot;&gt;
* &lt;b:bean
* class=&quot;org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper&quot;&gt;
* &lt;b:constructor-arg ref=&quot;userService&quot; /&gt;
* &lt;/b:bean&gt;
* &lt;/b:property&gt;
* &lt;b:property name=&quot;ticketValidator&quot;&gt;
* &lt;b:bean
* class=&quot;org.jasig.cas.client.validation.Cas20ProxyTicketValidator&quot;
* p:acceptAnyProxy=&quot;true&quot;
* p:proxyCallbackUrl=&quot;https://service.example.com/cas-sample/login/cas/proxyreceptor&quot;
* p:proxyGrantingTicketStorage-ref=&quot;pgtStorage&quot;&gt;
* &lt;b:constructor-arg value=&quot;https://login.example.org/cas&quot; /&gt;
* &lt;/b:bean&gt;
* &lt;/b:property&gt;
* &lt;b:property name=&quot;statelessTicketCache&quot;&gt;
* &lt;b:bean class=&quot;org.springframework.security.cas.authentication.EhCacheBasedTicketCache&quot;&gt;
* &lt;b:property name=&quot;cache&quot;&gt;
* &lt;b:bean class=&quot;net.sf.ehcache.Cache&quot;
* init-method=&quot;initialise&quot;
* destroy-method=&quot;dispose&quot;&gt;
* &lt;b:constructor-arg value=&quot;casTickets&quot;/&gt;
* &lt;b:constructor-arg value=&quot;50&quot;/&gt;
* &lt;b:constructor-arg value=&quot;true&quot;/&gt;
* &lt;b:constructor-arg value=&quot;false&quot;/&gt;
* &lt;b:constructor-arg value=&quot;3600&quot;/&gt;
* &lt;b:constructor-arg value=&quot;900&quot;/&gt;
* &lt;/b:bean&gt;
* &lt;/b:property&gt;
* &lt;/b:bean&gt;
* &lt;/b:property&gt;
* &lt;/b:bean&gt;
* </pre>
*
* @author Ben Alex
* @author Rob Winch
*/
public class CasAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
/**
* Used to identify a CAS request for a stateful user agent, such as a web browser.
*/
public static final String CAS_STATEFUL_IDENTIFIER = "_cas_stateful_";
/**
* Used to identify a CAS request for a stateless user agent, such as a remoting
* protocol client (e.g. Hessian, Burlap, SOAP etc). Results in a more aggressive
* caching strategy being used, as the absence of a <code>HttpSession</code> will
* result in a new authentication attempt on every request.
*/
public static final String CAS_STATELESS_IDENTIFIER = "_cas_stateless_";
/**
* The last portion of the receptor url, i.e. /proxy/receptor
*/
private RequestMatcher proxyReceptorMatcher;
/**
* The backing storage to store ProxyGrantingTicket requests.
*/
private ProxyGrantingTicketStorage proxyGrantingTicketStorage;
private String artifactParameter = ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER;
private boolean authenticateAllArtifacts;
private AuthenticationFailureHandler proxyFailureHandler = new SimpleUrlAuthenticationFailureHandler();
private SecurityContextRepository securityContextRepository = new NullSecurityContextRepository();
public CasAuthenticationFilter() {
super("/login/cas");
setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler());
}
@Override
protected final void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authResult) throws IOException, ServletException {
boolean continueFilterChain = proxyTicketRequest(serviceTicketRequest(request, response), request);
if (!continueFilterChain) {
super.successfulAuthentication(request, response, chain, authResult);
return;
}
this.logger.debug(
LogMessage.format("Authentication success. Updating SecurityContextHolder to contain: %s", authResult));
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authResult);
SecurityContextHolder.setContext(context);
this.securityContextRepository.saveContext(context, request, response);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
chain.doFilter(request, response);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException {
// if the request is a proxy request process it and return null to indicate the
// request has been processed
if (proxyReceptorRequest(request)) {
this.logger.debug("Responding to proxy receptor request");
CommonUtils.readAndRespondToProxyReceptorRequest(request, response, this.proxyGrantingTicketStorage);
return null;
}
boolean serviceTicketRequest = serviceTicketRequest(request, response);
String username = serviceTicketRequest ? CAS_STATEFUL_IDENTIFIER : CAS_STATELESS_IDENTIFIER;
String password = obtainArtifact(request);
if (password == null) {
this.logger.debug("Failed to obtain an artifact (cas ticket)");
password = "";
}
UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
password);
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
return this.getAuthenticationManager().authenticate(authRequest);
}
/**
* If present, gets the artifact (CAS ticket) from the {@link HttpServletRequest}.
* @param request
* @return if present the artifact from the {@link HttpServletRequest}, else null
*/
protected String obtainArtifact(HttpServletRequest request) {
return request.getParameter(this.artifactParameter);
}
/**
* Overridden to provide proxying capabilities.
*/
@Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
final boolean serviceTicketRequest = serviceTicketRequest(request, response);
final boolean result = serviceTicketRequest || proxyReceptorRequest(request)
|| (proxyTicketRequest(serviceTicketRequest, request));
if (this.logger.isDebugEnabled()) {
this.logger.debug("requiresAuthentication = " + result);
}
return result;
}
/**
* Sets the {@link SecurityContextRepository} to save the {@link SecurityContext} on
* authentication success. The default action is not to save the
* {@link SecurityContext}.
* @param securityContextRepository the {@link SecurityContextRepository} to use.
* Cannot be null.
*/
public void setSecurityContextRepository(SecurityContextRepository securityContextRepository) {
Assert.notNull(securityContextRepository, "securityContextRepository cannot be null");
this.securityContextRepository = securityContextRepository;
}
/**
* Sets the {@link AuthenticationFailureHandler} for proxy requests.
* @param proxyFailureHandler
*/
public final void setProxyAuthenticationFailureHandler(AuthenticationFailureHandler proxyFailureHandler) {
Assert.notNull(proxyFailureHandler, "proxyFailureHandler cannot be null");
this.proxyFailureHandler = proxyFailureHandler;
}
/**
* Wraps the {@link AuthenticationFailureHandler} to distinguish between handling
* proxy ticket authentication failures and service ticket failures.
*/
@Override
public final void setAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) {
super.setAuthenticationFailureHandler(new CasAuthenticationFailureHandler(failureHandler));
}
public final void setProxyReceptorUrl(final String proxyReceptorUrl) {
this.proxyReceptorMatcher = new AntPathRequestMatcher("/**" + proxyReceptorUrl);
}
public final void setProxyGrantingTicketStorage(final ProxyGrantingTicketStorage proxyGrantingTicketStorage) {
this.proxyGrantingTicketStorage = proxyGrantingTicketStorage;
}
public final void setServiceProperties(final ServiceProperties serviceProperties) {
this.artifactParameter = serviceProperties.getArtifactParameter();
this.authenticateAllArtifacts = serviceProperties.isAuthenticateAllArtifacts();
}
/**
* Indicates if the request is elgible to process a service ticket. This method exists
* for readability.
* @param request
* @param response
* @return
*/
private boolean serviceTicketRequest(HttpServletRequest request, HttpServletResponse response) {
boolean result = super.requiresAuthentication(request, response);
this.logger.debug(LogMessage.format("serviceTicketRequest = %s", result));
return result;
}
/**
* Indicates if the request is elgible to process a proxy ticket.
* @param request
* @return
*/
private boolean proxyTicketRequest(boolean serviceTicketRequest, HttpServletRequest request) {
if (serviceTicketRequest) {
return false;
}
boolean result = this.authenticateAllArtifacts && obtainArtifact(request) != null && !authenticated();
this.logger.debug(LogMessage.format("proxyTicketRequest = %s", result));
return result;
}
/**
* Determines if a user is already authenticated.
* @return
*/
private boolean authenticated() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication != null && authentication.isAuthenticated()
&& !(authentication instanceof AnonymousAuthenticationToken);
}
/**
* Indicates if the request is elgible to be processed as the proxy receptor.
* @param request
* @return
*/
private boolean proxyReceptorRequest(HttpServletRequest request) {
final boolean result = proxyReceptorConfigured() && this.proxyReceptorMatcher.matches(request);
this.logger.debug(LogMessage.format("proxyReceptorRequest = %s", result));
return result;
}
/**
* Determines if the {@link CasAuthenticationFilter} is configured to handle the proxy
* receptor requests.
* @return
*/
private boolean proxyReceptorConfigured() {
final boolean result = this.proxyGrantingTicketStorage != null && this.proxyReceptorMatcher != null;
this.logger.debug(LogMessage.format("proxyReceptorConfigured = %s", result));
return result;
}
/**
* A wrapper for the AuthenticationFailureHandler that will flex the
* {@link AuthenticationFailureHandler} that is used. The value
* {@link CasAuthenticationFilter#setProxyAuthenticationFailureHandler(AuthenticationFailureHandler)}
* will be used for proxy requests that fail. The value
* {@link CasAuthenticationFilter#setAuthenticationFailureHandler(AuthenticationFailureHandler)}
* will be used for service tickets that fail.
*/
private class CasAuthenticationFailureHandler implements AuthenticationFailureHandler {
private final AuthenticationFailureHandler serviceTicketFailureHandler;
CasAuthenticationFailureHandler(AuthenticationFailureHandler failureHandler) {
Assert.notNull(failureHandler, "failureHandler");
this.serviceTicketFailureHandler = failureHandler;
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
if (serviceTicketRequest(request, response)) {
this.serviceTicketFailureHandler.onAuthenticationFailure(request, response, exception);
}
else {
CasAuthenticationFilter.this.proxyFailureHandler.onAuthenticationFailure(request, response, exception);
}
}
}
}
@@ -0,0 +1,146 @@
/*
* Copyright 2011-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web.authentication;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.security.web.util.UrlUtils;
import org.springframework.util.Assert;
/**
* A default implementation of {@link ServiceAuthenticationDetails} that figures out the
* value for {@link #getServiceUrl()} by inspecting the current {@link HttpServletRequest}
* and using the current URL minus the artifact and the corresponding value.
*
* @author Rob Winch
*/
final class DefaultServiceAuthenticationDetails extends WebAuthenticationDetails
implements ServiceAuthenticationDetails {
private static final long serialVersionUID = 6192409090610517700L;
private final String serviceUrl;
/**
* Creates a new instance
* @param request the current {@link HttpServletRequest} to obtain the
* {@link #getServiceUrl()} from.
* @param artifactPattern the {@link Pattern} that will be used to clean up the query
* string from containing the artifact name and value. This can be created using
* {@link #createArtifactPattern(String)}.
*/
DefaultServiceAuthenticationDetails(String casService, HttpServletRequest request, Pattern artifactPattern)
throws MalformedURLException {
super(request);
URL casServiceUrl = new URL(casService);
int port = getServicePort(casServiceUrl);
final String query = getQueryString(request, artifactPattern);
this.serviceUrl = UrlUtils.buildFullRequestUrl(casServiceUrl.getProtocol(), casServiceUrl.getHost(), port,
request.getRequestURI(), query);
}
/**
* Returns the current URL minus the artifact parameter and its value, if present.
* @see org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails#getServiceUrl()
*/
@Override
public String getServiceUrl() {
return this.serviceUrl;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj) || !(obj instanceof DefaultServiceAuthenticationDetails)) {
return false;
}
ServiceAuthenticationDetails that = (ServiceAuthenticationDetails) obj;
return this.serviceUrl.equals(that.getServiceUrl());
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + this.serviceUrl.hashCode();
return result;
}
@Override
public String toString() {
StringBuilder result = new StringBuilder();
result.append(super.toString());
result.append("ServiceUrl: ");
result.append(this.serviceUrl);
return result.toString();
}
/**
* If present, removes the artifactParameterName and the corresponding value from the
* query String.
* @param request
* @return the query String minus the artifactParameterName and the corresponding
* value.
*/
private String getQueryString(final HttpServletRequest request, final Pattern artifactPattern) {
final String query = request.getQueryString();
if (query == null) {
return null;
}
String result = artifactPattern.matcher(query).replaceFirst("");
if (result.length() == 0) {
return null;
}
// strip off the trailing & only if the artifact was the first query param
return result.startsWith("&") ? result.substring(1) : result;
}
/**
* Creates a {@link Pattern} that can be passed into the constructor. This allows the
* {@link Pattern} to be reused for every instance of
* {@link DefaultServiceAuthenticationDetails}.
* @param artifactParameterName
* @return
*/
static Pattern createArtifactPattern(String artifactParameterName) {
Assert.hasLength(artifactParameterName, "artifactParameterName is expected to have a length");
return Pattern.compile("&?" + Pattern.quote(artifactParameterName) + "=[^&]*");
}
/**
* Gets the port from the casServiceURL ensuring to return the proper value if the
* default port is being used.
* @param casServiceUrl the casServerUrl to be used (i.e.
* "https://example.com/context/login/cas")
* @return the port that is configured for the casServerUrl
*/
private static int getServicePort(URL casServiceUrl) {
int port = casServiceUrl.getPort();
if (port == -1) {
port = casServiceUrl.getDefaultPort();
}
return port;
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2011-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web.authentication;
import java.io.Serializable;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.core.Authentication;
/**
* In order for the {@link CasAuthenticationProvider} to provide the correct service url
* to authenticate the ticket, the returned value of {@link Authentication#getDetails()}
* should implement this interface when tickets can be sent to any URL rather than only
* {@link ServiceProperties#getService()}.
*
* @author Rob Winch
* @see ServiceAuthenticationDetailsSource
*/
public interface ServiceAuthenticationDetails extends Serializable {
/**
* Gets the absolute service url (i.e. https://example.com/service/).
* @return the service url. Cannot be <code>null</code>.
*/
String getServiceUrl();
}
@@ -0,0 +1,83 @@
/*
* Copyright 2011-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web.authentication;
import java.net.MalformedURLException;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.util.Assert;
/**
* The {@code AuthenticationDetailsSource} that is set on the
* {@code CasAuthenticationFilter} should return a value that implements
* {@code ServiceAuthenticationDetails} if the application needs to authenticate dynamic
* service urls. The
* {@code ServiceAuthenticationDetailsSource#buildDetails(HttpServletRequest)} creates a
* default {@code ServiceAuthenticationDetails}.
*
* @author Rob Winch
*/
public class ServiceAuthenticationDetailsSource
implements AuthenticationDetailsSource<HttpServletRequest, ServiceAuthenticationDetails> {
private final Pattern artifactPattern;
private ServiceProperties serviceProperties;
/**
* Creates an implementation that uses the specified ServiceProperties and the default
* CAS artifactParameterName.
* @param serviceProperties The ServiceProperties to use to construct the serviceUrl.
*/
public ServiceAuthenticationDetailsSource(ServiceProperties serviceProperties) {
this(serviceProperties, ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER);
}
/**
* Creates an implementation that uses the specified artifactParameterName
* @param serviceProperties The ServiceProperties to use to construct the serviceUrl.
* @param artifactParameterName the artifactParameterName that is removed from the
* current URL. The result becomes the service url. Cannot be null and cannot be an
* empty String.
*/
public ServiceAuthenticationDetailsSource(ServiceProperties serviceProperties, String artifactParameterName) {
Assert.notNull(serviceProperties, "serviceProperties cannot be null");
this.serviceProperties = serviceProperties;
this.artifactPattern = DefaultServiceAuthenticationDetails.createArtifactPattern(artifactParameterName);
}
/**
* @param context the {@code HttpServletRequest} object.
* @return the {@code ServiceAuthenticationDetails} containing information about the
* current request
*/
@Override
public ServiceAuthenticationDetails buildDetails(HttpServletRequest context) {
try {
return new DefaultServiceAuthenticationDetails(this.serviceProperties.getService(), context,
this.artifactPattern);
}
catch (MalformedURLException ex) {
throw new RuntimeException(ex);
}
}
}
@@ -0,0 +1,21 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Authentication processing mechanisms which respond to the submission of authentication
* credentials using CAS.
*/
package org.springframework.security.cas.web.authentication;
@@ -0,0 +1,20 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Authenticates standard web browser users via CAS.
*/
package org.springframework.security.cas.web;
@@ -0,0 +1,45 @@
/*
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.authentication;
import java.util.ArrayList;
import java.util.List;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.AssertionImpl;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
/**
* @author Scott Battaglia
* @since 2.0
*
*/
public abstract class AbstractStatelessTicketCacheTests {
protected CasAuthenticationToken getToken() {
List<String> proxyList = new ArrayList<>();
proxyList.add("https://localhost/newPortal/login/cas");
User user = new User("rod", "password", true, true, true, true,
AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"));
final Assertion assertion = new AssertionImpl("rod");
return new CasAuthenticationToken("key", user, "ST-0-ER94xMJmn6pha35CQRoZ",
AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"), user, assertion);
}
}
@@ -0,0 +1,382 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.authentication;
import java.util.HashMap;
import java.util.Map;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.AssertionImpl;
import org.jasig.cas.client.validation.TicketValidator;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.cas.web.authentication.ServiceAuthenticationDetails;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests {@link CasAuthenticationProvider}.
*
* @author Ben Alex
* @author Scott Battaglia
*/
@SuppressWarnings("unchecked")
public class CasAuthenticationProviderTests {
private UserDetails makeUserDetails() {
return new User("user", "password", true, true, true, true,
AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO"));
}
private UserDetails makeUserDetailsFromAuthoritiesPopulator() {
return new User("user", "password", true, true, true, true,
AuthorityUtils.createAuthorityList("ROLE_A", "ROLE_B"));
}
private ServiceProperties makeServiceProperties() {
final ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setSendRenew(false);
serviceProperties.setService("http://test.com");
return serviceProperties;
}
@Test
public void statefulAuthenticationIsSuccessful() throws Exception {
CasAuthenticationProvider cap = new CasAuthenticationProvider();
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
cap.setKey("qwerty");
StatelessTicketCache cache = new MockStatelessTicketCache();
cap.setStatelessTicketCache(cache);
cap.setServiceProperties(makeServiceProperties());
cap.setTicketValidator(new MockTicketValidator(true));
cap.afterPropertiesSet();
UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken
.unauthenticated(CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER, "ST-123");
token.setDetails("details");
Authentication result = cap.authenticate(token);
// Confirm ST-123 was NOT added to the cache
assertThat(cache.getByTicketId("ST-456") == null).isTrue();
if (!(result instanceof CasAuthenticationToken)) {
fail("Should have returned a CasAuthenticationToken");
}
CasAuthenticationToken casResult = (CasAuthenticationToken) result;
assertThat(casResult.getPrincipal()).isEqualTo(makeUserDetailsFromAuthoritiesPopulator());
assertThat(casResult.getCredentials()).isEqualTo("ST-123");
assertThat(casResult.getAuthorities()).contains(new SimpleGrantedAuthority("ROLE_A"));
assertThat(casResult.getAuthorities()).contains(new SimpleGrantedAuthority("ROLE_B"));
assertThat(casResult.getKeyHash()).isEqualTo(cap.getKey().hashCode());
assertThat(casResult.getDetails()).isEqualTo("details");
// Now confirm the CasAuthenticationToken is automatically re-accepted.
// To ensure TicketValidator not called again, set it to deliver an exception...
cap.setTicketValidator(new MockTicketValidator(false));
Authentication laterResult = cap.authenticate(result);
assertThat(laterResult).isEqualTo(result);
}
@Test
public void statelessAuthenticationIsSuccessful() throws Exception {
CasAuthenticationProvider cap = new CasAuthenticationProvider();
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
cap.setKey("qwerty");
StatelessTicketCache cache = new MockStatelessTicketCache();
cap.setStatelessTicketCache(cache);
cap.setTicketValidator(new MockTicketValidator(true));
cap.setServiceProperties(makeServiceProperties());
cap.afterPropertiesSet();
UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken
.unauthenticated(CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, "ST-456");
token.setDetails("details");
Authentication result = cap.authenticate(token);
// Confirm ST-456 was added to the cache
assertThat(cache.getByTicketId("ST-456") != null).isTrue();
if (!(result instanceof CasAuthenticationToken)) {
fail("Should have returned a CasAuthenticationToken");
}
assertThat(result.getPrincipal()).isEqualTo(makeUserDetailsFromAuthoritiesPopulator());
assertThat(result.getCredentials()).isEqualTo("ST-456");
assertThat(result.getDetails()).isEqualTo("details");
// Now try to authenticate again. To ensure TicketValidator not
// called again, set it to deliver an exception...
cap.setTicketValidator(new MockTicketValidator(false));
// Previously created UsernamePasswordAuthenticationToken is OK
Authentication newResult = cap.authenticate(token);
assertThat(newResult.getPrincipal()).isEqualTo(makeUserDetailsFromAuthoritiesPopulator());
assertThat(newResult.getCredentials()).isEqualTo("ST-456");
}
@Test
public void authenticateAllNullService() throws Exception {
String serviceUrl = "https://service/context";
ServiceAuthenticationDetails details = mock(ServiceAuthenticationDetails.class);
given(details.getServiceUrl()).willReturn(serviceUrl);
TicketValidator validator = mock(TicketValidator.class);
given(validator.validate(any(String.class), any(String.class))).willReturn(new AssertionImpl("rod"));
ServiceProperties serviceProperties = makeServiceProperties();
serviceProperties.setAuthenticateAllArtifacts(true);
CasAuthenticationProvider cap = new CasAuthenticationProvider();
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
cap.setKey("qwerty");
cap.setTicketValidator(validator);
cap.setServiceProperties(serviceProperties);
cap.afterPropertiesSet();
String ticket = "ST-456";
UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken
.unauthenticated(CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, ticket);
Authentication result = cap.authenticate(token);
}
@Test
public void authenticateAllAuthenticationIsSuccessful() throws Exception {
String serviceUrl = "https://service/context";
ServiceAuthenticationDetails details = mock(ServiceAuthenticationDetails.class);
given(details.getServiceUrl()).willReturn(serviceUrl);
TicketValidator validator = mock(TicketValidator.class);
given(validator.validate(any(String.class), any(String.class))).willReturn(new AssertionImpl("rod"));
ServiceProperties serviceProperties = makeServiceProperties();
serviceProperties.setAuthenticateAllArtifacts(true);
CasAuthenticationProvider cap = new CasAuthenticationProvider();
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
cap.setKey("qwerty");
cap.setTicketValidator(validator);
cap.setServiceProperties(serviceProperties);
cap.afterPropertiesSet();
String ticket = "ST-456";
UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken
.unauthenticated(CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER, ticket);
Authentication result = cap.authenticate(token);
verify(validator).validate(ticket, serviceProperties.getService());
serviceProperties.setAuthenticateAllArtifacts(true);
result = cap.authenticate(token);
verify(validator, times(2)).validate(ticket, serviceProperties.getService());
token.setDetails(details);
result = cap.authenticate(token);
verify(validator).validate(ticket, serviceUrl);
serviceProperties.setAuthenticateAllArtifacts(false);
serviceProperties.setService(null);
cap.setServiceProperties(serviceProperties);
cap.afterPropertiesSet();
result = cap.authenticate(token);
verify(validator, times(2)).validate(ticket, serviceUrl);
token.setDetails(new WebAuthenticationDetails(new MockHttpServletRequest()));
assertThatIllegalStateException().isThrownBy(() -> cap.authenticate(token));
cap.setServiceProperties(null);
cap.afterPropertiesSet();
assertThatIllegalStateException().isThrownBy(() -> cap.authenticate(token));
}
@Test
public void missingTicketIdIsDetected() throws Exception {
CasAuthenticationProvider cap = new CasAuthenticationProvider();
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
cap.setKey("qwerty");
StatelessTicketCache cache = new MockStatelessTicketCache();
cap.setStatelessTicketCache(cache);
cap.setTicketValidator(new MockTicketValidator(true));
cap.setServiceProperties(makeServiceProperties());
cap.afterPropertiesSet();
UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken
.unauthenticated(CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER, "");
assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> cap.authenticate(token));
}
@Test
public void invalidKeyIsDetected() throws Exception {
final Assertion assertion = new AssertionImpl("test");
CasAuthenticationProvider cap = new CasAuthenticationProvider();
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
cap.setKey("qwerty");
StatelessTicketCache cache = new MockStatelessTicketCache();
cap.setStatelessTicketCache(cache);
cap.setTicketValidator(new MockTicketValidator(true));
cap.setServiceProperties(makeServiceProperties());
cap.afterPropertiesSet();
CasAuthenticationToken token = new CasAuthenticationToken("WRONG_KEY", makeUserDetails(), "credentials",
AuthorityUtils.createAuthorityList("XX"), makeUserDetails(), assertion);
assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> cap.authenticate(token));
}
@Test
public void detectsMissingAuthoritiesPopulator() throws Exception {
CasAuthenticationProvider cap = new CasAuthenticationProvider();
cap.setKey("qwerty");
cap.setStatelessTicketCache(new MockStatelessTicketCache());
cap.setTicketValidator(new MockTicketValidator(true));
cap.setServiceProperties(makeServiceProperties());
assertThatIllegalArgumentException().isThrownBy(() -> cap.afterPropertiesSet());
}
@Test
public void detectsMissingKey() throws Exception {
CasAuthenticationProvider cap = new CasAuthenticationProvider();
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
cap.setStatelessTicketCache(new MockStatelessTicketCache());
cap.setTicketValidator(new MockTicketValidator(true));
cap.setServiceProperties(makeServiceProperties());
assertThatIllegalArgumentException().isThrownBy(() -> cap.afterPropertiesSet());
}
@Test
public void detectsMissingStatelessTicketCache() throws Exception {
CasAuthenticationProvider cap = new CasAuthenticationProvider();
// set this explicitly to null to test failure
cap.setStatelessTicketCache(null);
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
cap.setKey("qwerty");
cap.setTicketValidator(new MockTicketValidator(true));
cap.setServiceProperties(makeServiceProperties());
assertThatIllegalArgumentException().isThrownBy(() -> cap.afterPropertiesSet());
}
@Test
public void detectsMissingTicketValidator() throws Exception {
CasAuthenticationProvider cap = new CasAuthenticationProvider();
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
cap.setKey("qwerty");
cap.setStatelessTicketCache(new MockStatelessTicketCache());
cap.setServiceProperties(makeServiceProperties());
assertThatIllegalArgumentException().isThrownBy(() -> cap.afterPropertiesSet());
}
@Test
public void gettersAndSettersMatch() throws Exception {
CasAuthenticationProvider cap = new CasAuthenticationProvider();
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
cap.setKey("qwerty");
cap.setStatelessTicketCache(new MockStatelessTicketCache());
cap.setTicketValidator(new MockTicketValidator(true));
cap.setServiceProperties(makeServiceProperties());
cap.afterPropertiesSet();
// TODO disabled because why do we need to expose this?
// assertThat(cap.getUserDetailsService() != null).isTrue();
assertThat(cap.getKey()).isEqualTo("qwerty");
assertThat(cap.getStatelessTicketCache() != null).isTrue();
assertThat(cap.getTicketValidator() != null).isTrue();
}
@Test
public void ignoresClassesItDoesNotSupport() throws Exception {
CasAuthenticationProvider cap = new CasAuthenticationProvider();
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
cap.setKey("qwerty");
cap.setStatelessTicketCache(new MockStatelessTicketCache());
cap.setTicketValidator(new MockTicketValidator(true));
cap.setServiceProperties(makeServiceProperties());
cap.afterPropertiesSet();
TestingAuthenticationToken token = new TestingAuthenticationToken("user", "password", "ROLE_A");
assertThat(cap.supports(TestingAuthenticationToken.class)).isFalse();
// Try it anyway
assertThat(cap.authenticate(token)).isNull();
}
@Test
public void ignoresUsernamePasswordAuthenticationTokensWithoutCasIdentifiersAsPrincipal() throws Exception {
CasAuthenticationProvider cap = new CasAuthenticationProvider();
cap.setAuthenticationUserDetailsService(new MockAuthoritiesPopulator());
cap.setKey("qwerty");
cap.setStatelessTicketCache(new MockStatelessTicketCache());
cap.setTicketValidator(new MockTicketValidator(true));
cap.setServiceProperties(makeServiceProperties());
cap.afterPropertiesSet();
UsernamePasswordAuthenticationToken token = UsernamePasswordAuthenticationToken
.authenticated("some_normal_user", "password", AuthorityUtils.createAuthorityList("ROLE_A"));
assertThat(cap.authenticate(token)).isNull();
}
@Test
public void supportsRequiredTokens() {
CasAuthenticationProvider cap = new CasAuthenticationProvider();
assertThat(cap.supports(UsernamePasswordAuthenticationToken.class)).isTrue();
assertThat(cap.supports(CasAuthenticationToken.class)).isTrue();
}
private class MockAuthoritiesPopulator implements AuthenticationUserDetailsService {
@Override
public UserDetails loadUserDetails(final Authentication token) throws UsernameNotFoundException {
return makeUserDetailsFromAuthoritiesPopulator();
}
}
private class MockStatelessTicketCache implements StatelessTicketCache {
private Map<String, CasAuthenticationToken> cache = new HashMap<>();
@Override
public CasAuthenticationToken getByTicketId(String serviceTicket) {
return this.cache.get(serviceTicket);
}
@Override
public void putTicketInCache(CasAuthenticationToken token) {
this.cache.put(token.getCredentials().toString(), token);
}
@Override
public void removeTicketFromCache(CasAuthenticationToken token) {
throw new UnsupportedOperationException("mock method not implemented");
}
@Override
public void removeTicketFromCache(String serviceTicket) {
throw new UnsupportedOperationException("mock method not implemented");
}
}
private class MockTicketValidator implements TicketValidator {
private boolean returnTicket;
MockTicketValidator(boolean returnTicket) {
this.returnTicket = returnTicket;
}
@Override
public Assertion validate(final String ticket, final String service) {
if (this.returnTicket) {
return new AssertionImpl("rod");
}
throw new BadCredentialsException("As requested from mock");
}
}
}
@@ -0,0 +1,169 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.authentication;
import java.util.Collections;
import java.util.List;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.AssertionImpl;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests {@link CasAuthenticationToken}.
*
* @author Ben Alex
*/
public class CasAuthenticationTokenTests {
private final List<GrantedAuthority> ROLES = AuthorityUtils.createAuthorityList("ROLE_ONE", "ROLE_TWO");
private UserDetails makeUserDetails() {
return makeUserDetails("user");
}
private UserDetails makeUserDetails(final String name) {
return new User(name, "password", true, true, true, true, this.ROLES);
}
@Test
public void testConstructorRejectsNulls() {
Assertion assertion = new AssertionImpl("test");
assertThatIllegalArgumentException().isThrownBy(() -> new CasAuthenticationToken(null, makeUserDetails(),
"Password", this.ROLES, makeUserDetails(), assertion));
assertThatIllegalArgumentException().isThrownBy(
() -> new CasAuthenticationToken("key", null, "Password", this.ROLES, makeUserDetails(), assertion));
assertThatIllegalArgumentException().isThrownBy(() -> new CasAuthenticationToken("key", makeUserDetails(), null,
this.ROLES, makeUserDetails(), assertion));
assertThatIllegalArgumentException().isThrownBy(() -> new CasAuthenticationToken("key", makeUserDetails(),
"Password", this.ROLES, makeUserDetails(), null));
assertThatIllegalArgumentException().isThrownBy(
() -> new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES, null, assertion));
assertThatIllegalArgumentException().isThrownBy(() -> new CasAuthenticationToken("key", makeUserDetails(),
"Password", AuthorityUtils.createAuthorityList("ROLE_1", null), makeUserDetails(), assertion));
}
@Test
public void constructorWhenEmptyKeyThenThrowsException() {
assertThatIllegalArgumentException().isThrownBy(
() -> new CasAuthenticationToken("", "user", "password", Collections.<GrantedAuthority>emptyList(),
new User("user", "password", Collections.<GrantedAuthority>emptyList()), null));
}
@Test
public void testEqualsWhenEqual() {
final Assertion assertion = new AssertionImpl("test");
CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
makeUserDetails(), assertion);
CasAuthenticationToken token2 = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
makeUserDetails(), assertion);
assertThat(token2).isEqualTo(token1);
}
@Test
public void testGetters() {
// Build the proxy list returned in the ticket from CAS
final Assertion assertion = new AssertionImpl("test");
CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
makeUserDetails(), assertion);
assertThat(token.getKeyHash()).isEqualTo("key".hashCode());
assertThat(token.getPrincipal()).isEqualTo(makeUserDetails());
assertThat(token.getCredentials()).isEqualTo("Password");
assertThat(token.getAuthorities()).contains(new SimpleGrantedAuthority("ROLE_ONE"));
assertThat(token.getAuthorities()).contains(new SimpleGrantedAuthority("ROLE_TWO"));
assertThat(token.getAssertion()).isEqualTo(assertion);
assertThat(token.getUserDetails().getUsername()).isEqualTo(makeUserDetails().getUsername());
}
@Test
public void testNoArgConstructorDoesntExist() {
assertThatExceptionOfType(NoSuchMethodException.class)
.isThrownBy(() -> CasAuthenticationToken.class.getDeclaredConstructor((Class[]) null));
}
@Test
public void testNotEqualsDueToAbstractParentEqualsCheck() {
final Assertion assertion = new AssertionImpl("test");
CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
makeUserDetails(), assertion);
CasAuthenticationToken token2 = new CasAuthenticationToken("key", makeUserDetails("OTHER_NAME"), "Password",
this.ROLES, makeUserDetails(), assertion);
assertThat(!token1.equals(token2)).isTrue();
}
@Test
public void testNotEqualsDueToDifferentAuthenticationClass() {
final Assertion assertion = new AssertionImpl("test");
CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
makeUserDetails(), assertion);
UsernamePasswordAuthenticationToken token2 = UsernamePasswordAuthenticationToken.authenticated("Test",
"Password", this.ROLES);
assertThat(!token1.equals(token2)).isTrue();
}
@Test
public void testNotEqualsDueToKey() {
final Assertion assertion = new AssertionImpl("test");
CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
makeUserDetails(), assertion);
CasAuthenticationToken token2 = new CasAuthenticationToken("DIFFERENT_KEY", makeUserDetails(), "Password",
this.ROLES, makeUserDetails(), assertion);
assertThat(!token1.equals(token2)).isTrue();
}
@Test
public void testNotEqualsDueToAssertion() {
final Assertion assertion = new AssertionImpl("test");
final Assertion assertion2 = new AssertionImpl("test");
CasAuthenticationToken token1 = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
makeUserDetails(), assertion);
CasAuthenticationToken token2 = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
makeUserDetails(), assertion2);
assertThat(!token1.equals(token2)).isTrue();
}
@Test
public void testSetAuthenticated() {
final Assertion assertion = new AssertionImpl("test");
CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
makeUserDetails(), assertion);
assertThat(token.isAuthenticated()).isTrue();
token.setAuthenticated(false);
assertThat(!token.isAuthenticated()).isTrue();
}
@Test
public void testToString() {
final Assertion assertion = new AssertionImpl("test");
CasAuthenticationToken token = new CasAuthenticationToken("key", makeUserDetails(), "Password", this.ROLES,
makeUserDetails(), assertion);
String result = token.toString();
assertThat(result.lastIndexOf("Credentials (Service/Proxy Ticket):") != -1).isTrue();
}
}
@@ -0,0 +1,76 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.authentication;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Ehcache;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests {@link EhCacheBasedTicketCache}.
*
* @author Ben Alex
*/
public class EhCacheBasedTicketCacheTests extends AbstractStatelessTicketCacheTests {
private static CacheManager cacheManager;
@BeforeAll
public static void initCacheManaer() {
cacheManager = CacheManager.create();
cacheManager.addCache(new Cache("castickets", 500, false, false, 30, 30));
}
@AfterAll
public static void shutdownCacheManager() {
cacheManager.removalAll();
cacheManager.shutdown();
}
@Test
public void testCacheOperation() throws Exception {
EhCacheBasedTicketCache cache = new EhCacheBasedTicketCache();
cache.setCache(cacheManager.getCache("castickets"));
cache.afterPropertiesSet();
final CasAuthenticationToken token = getToken();
// Check it gets stored in the cache
cache.putTicketInCache(token);
assertThat(cache.getByTicketId("ST-0-ER94xMJmn6pha35CQRoZ")).isEqualTo(token);
// Check it gets removed from the cache
cache.removeTicketFromCache(getToken());
assertThat(cache.getByTicketId("ST-0-ER94xMJmn6pha35CQRoZ")).isNull();
// Check it doesn't return values for null or unknown service tickets
assertThat(cache.getByTicketId(null)).isNull();
assertThat(cache.getByTicketId("UNKNOWN_SERVICE_TICKET")).isNull();
}
@Test
public void testStartupDetectsMissingCache() throws Exception {
EhCacheBasedTicketCache cache = new EhCacheBasedTicketCache();
assertThatIllegalArgumentException().isThrownBy(cache::afterPropertiesSet);
Ehcache myCache = cacheManager.getCache("castickets");
cache.setCache(myCache);
assertThat(cache.getCache()).isEqualTo(myCache);
}
}
@@ -0,0 +1,46 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.authentication;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test cases for the @link {@link NullStatelessTicketCache}
*
* @author Scott Battaglia
*
*/
public class NullStatelessTicketCacheTests extends AbstractStatelessTicketCacheTests {
private StatelessTicketCache cache = new NullStatelessTicketCache();
@Test
public void testGetter() {
assertThat(this.cache.getByTicketId(null)).isNull();
assertThat(this.cache.getByTicketId("test")).isNull();
}
@Test
public void testInsertAndGet() {
final CasAuthenticationToken token = getToken();
this.cache.putTicketInCache(token);
assertThat(this.cache.getByTicketId((String) token.getCredentials())).isNull();
}
}
@@ -0,0 +1,65 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.authentication;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests
* {@link org.springframework.security.cas.authentication.SpringCacheBasedTicketCache}.
*
* @author Marten Deinum
* @since 3.2
*/
public class SpringCacheBasedTicketCacheTests extends AbstractStatelessTicketCacheTests {
private static CacheManager cacheManager;
@BeforeAll
public static void initCacheManaer() {
cacheManager = new ConcurrentMapCacheManager();
cacheManager.getCache("castickets");
}
@Test
public void testCacheOperation() throws Exception {
SpringCacheBasedTicketCache cache = new SpringCacheBasedTicketCache(cacheManager.getCache("castickets"));
final CasAuthenticationToken token = getToken();
// Check it gets stored in the cache
cache.putTicketInCache(token);
assertThat(cache.getByTicketId("ST-0-ER94xMJmn6pha35CQRoZ")).isEqualTo(token);
// Check it gets removed from the cache
cache.removeTicketFromCache(getToken());
assertThat(cache.getByTicketId("ST-0-ER94xMJmn6pha35CQRoZ")).isNull();
// Check it doesn't return values for null or unknown service tickets
assertThat(cache.getByTicketId(null)).isNull();
assertThat(cache.getByTicketId("UNKNOWN_SERVICE_TICKET")).isNull();
}
@Test
public void testStartupDetectsMissingCache() throws Exception {
assertThatIllegalArgumentException().isThrownBy(() -> new SpringCacheBasedTicketCache(null));
}
}
@@ -0,0 +1,153 @@
/*
* Copyright 2015-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.jackson2;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jasig.cas.client.authentication.AttributePrincipalImpl;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.AssertionImpl;
import org.json.JSONException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.security.cas.authentication.CasAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.jackson2.SecurityJackson2Modules;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Jitendra Singh
* @since 4.2
*/
public class CasAuthenticationTokenMixinTests {
private static final String KEY = "casKey";
private static final String PASSWORD = "\"1234\"";
private static final Date START_DATE = new Date();
private static final Date END_DATE = new Date();
public static final String AUTHORITY_JSON = "{\"@class\": \"org.springframework.security.core.authority.SimpleGrantedAuthority\", \"authority\": \"ROLE_USER\"}";
public static final String AUTHORITIES_SET_JSON = "[\"java.util.Collections$UnmodifiableSet\", [" + AUTHORITY_JSON
+ "]]";
public static final String AUTHORITIES_ARRAYLIST_JSON = "[\"java.util.Collections$UnmodifiableRandomAccessList\", ["
+ AUTHORITY_JSON + "]]";
// @formatter:off
public static final String USER_JSON = "{"
+ "\"@class\": \"org.springframework.security.core.userdetails.User\", "
+ "\"username\": \"admin\","
+ " \"password\": " + PASSWORD + ", "
+ "\"accountNonExpired\": true, "
+ "\"accountNonLocked\": true, "
+ "\"credentialsNonExpired\": true, "
+ "\"enabled\": true, "
+ "\"authorities\": " + AUTHORITIES_SET_JSON
+ "}";
// @formatter:on
private static final String CAS_TOKEN_JSON = "{"
+ "\"@class\": \"org.springframework.security.cas.authentication.CasAuthenticationToken\", "
+ "\"keyHash\": " + KEY.hashCode() + "," + "\"principal\": " + USER_JSON + ", " + "\"credentials\": "
+ PASSWORD + ", " + "\"authorities\": " + AUTHORITIES_ARRAYLIST_JSON + "," + "\"userDetails\": " + USER_JSON
+ "," + "\"authenticated\": true, " + "\"details\": null," + "\"assertion\": {"
+ "\"@class\": \"org.jasig.cas.client.validation.AssertionImpl\", " + "\"principal\": {"
+ "\"@class\": \"org.jasig.cas.client.authentication.AttributePrincipalImpl\", "
+ "\"name\": \"assertName\", " + "\"attributes\": {\"@class\": \"java.util.Collections$EmptyMap\"}, "
+ "\"proxyGrantingTicket\": null, " + "\"proxyRetriever\": null" + "}, "
+ "\"validFromDate\": [\"java.util.Date\", " + START_DATE.getTime() + "], "
+ "\"validUntilDate\": [\"java.util.Date\", " + END_DATE.getTime() + "],"
+ "\"authenticationDate\": [\"java.util.Date\", " + START_DATE.getTime() + "], "
+ "\"attributes\": {\"@class\": \"java.util.Collections$EmptyMap\"}" + "}" + "}";
private static final String CAS_TOKEN_CLEARED_JSON = CAS_TOKEN_JSON.replaceFirst(PASSWORD, "null");
protected ObjectMapper mapper;
@BeforeEach
public void setup() {
this.mapper = new ObjectMapper();
ClassLoader loader = getClass().getClassLoader();
this.mapper.registerModules(SecurityJackson2Modules.getModules(loader));
}
@Test
public void serializeCasAuthenticationTest() throws JsonProcessingException, JSONException {
CasAuthenticationToken token = createCasAuthenticationToken();
String actualJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(CAS_TOKEN_JSON, actualJson, true);
}
@Test
public void serializeCasAuthenticationTestAfterEraseCredentialInvoked()
throws JsonProcessingException, JSONException {
CasAuthenticationToken token = createCasAuthenticationToken();
token.eraseCredentials();
String actualJson = this.mapper.writeValueAsString(token);
JSONAssert.assertEquals(CAS_TOKEN_CLEARED_JSON, actualJson, true);
}
@Test
public void deserializeCasAuthenticationTestAfterEraseCredentialInvoked() throws Exception {
CasAuthenticationToken token = this.mapper.readValue(CAS_TOKEN_CLEARED_JSON, CasAuthenticationToken.class);
assertThat(((UserDetails) token.getPrincipal()).getPassword()).isNull();
}
@Test
public void deserializeCasAuthenticationTest() throws IOException {
CasAuthenticationToken token = this.mapper.readValue(CAS_TOKEN_JSON, CasAuthenticationToken.class);
assertThat(token).isNotNull();
assertThat(token.getPrincipal()).isNotNull().isInstanceOf(User.class);
assertThat(((User) token.getPrincipal()).getUsername()).isEqualTo("admin");
assertThat(((User) token.getPrincipal()).getPassword()).isEqualTo("1234");
assertThat(token.getUserDetails()).isNotNull().isInstanceOf(User.class);
assertThat(token.getAssertion()).isNotNull().isInstanceOf(AssertionImpl.class);
assertThat(token.getKeyHash()).isEqualTo(KEY.hashCode());
assertThat(token.getUserDetails().getAuthorities()).extracting(GrantedAuthority::getAuthority)
.containsOnly("ROLE_USER");
assertThat(token.getAssertion().getAuthenticationDate()).isEqualTo(START_DATE);
assertThat(token.getAssertion().getValidFromDate()).isEqualTo(START_DATE);
assertThat(token.getAssertion().getValidUntilDate()).isEqualTo(END_DATE);
assertThat(token.getAssertion().getPrincipal().getName()).isEqualTo("assertName");
assertThat(token.getAssertion().getAttributes()).hasSize(0);
}
private CasAuthenticationToken createCasAuthenticationToken() {
User principal = new User("admin", "1234", Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
Collection<? extends GrantedAuthority> authorities = Collections
.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
Assertion assertion = new AssertionImpl(new AttributePrincipalImpl("assertName"), START_DATE, END_DATE,
START_DATE, Collections.<String, Object>emptyMap());
return new CasAuthenticationToken(KEY, principal, principal.getPassword(), authorities,
new User("admin", "1234", authorities), assertion);
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.userdetails;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import org.junit.jupiter.api.Test;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* @author Luke Taylor
*/
public class GrantedAuthorityFromAssertionAttributesUserDetailsServiceTests {
@Test
public void correctlyExtractsNamedAttributesFromAssertionAndConvertsThemToAuthorities() {
GrantedAuthorityFromAssertionAttributesUserDetailsService uds = new GrantedAuthorityFromAssertionAttributesUserDetailsService(
new String[] { "a", "b", "c", "d" });
uds.setConvertToUpperCase(false);
Assertion assertion = mock(Assertion.class);
AttributePrincipal principal = mock(AttributePrincipal.class);
Map<String, Object> attributes = new HashMap<>();
attributes.put("a", Arrays.asList("role_a1", "role_a2"));
attributes.put("b", "role_b");
attributes.put("c", "role_c");
attributes.put("d", null);
attributes.put("someother", "unused");
given(assertion.getPrincipal()).willReturn(principal);
given(principal.getAttributes()).willReturn(attributes);
given(principal.getName()).willReturn("somebody");
CasAssertionAuthenticationToken token = new CasAssertionAuthenticationToken(assertion, "ticket");
UserDetails user = uds.loadUserDetails(token);
Set<String> roles = AuthorityUtils.authorityListToSet(user.getAuthorities());
assertThat(roles).containsExactlyInAnyOrder("role_a1", "role_a2", "role_b", "role_c");
}
}
@@ -0,0 +1,98 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web;
import java.net.URLEncoder;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.cas.ServiceProperties;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests {@link CasAuthenticationEntryPoint}.
*
* @author Ben Alex
*/
public class CasAuthenticationEntryPointTests {
@Test
public void testDetectsMissingLoginFormUrl() throws Exception {
CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint();
ep.setServiceProperties(new ServiceProperties());
assertThatIllegalArgumentException().isThrownBy(ep::afterPropertiesSet)
.withMessage("loginUrl must be specified");
}
@Test
public void testDetectsMissingServiceProperties() throws Exception {
CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint();
ep.setLoginUrl("https://cas/login");
assertThatIllegalArgumentException().isThrownBy(ep::afterPropertiesSet)
.withMessage("serviceProperties must be specified");
}
@Test
public void testGettersSetters() {
CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint();
ep.setLoginUrl("https://cas/login");
assertThat(ep.getLoginUrl()).isEqualTo("https://cas/login");
ep.setServiceProperties(new ServiceProperties());
assertThat(ep.getServiceProperties() != null).isTrue();
}
@Test
public void testNormalOperationWithRenewFalse() throws Exception {
ServiceProperties sp = new ServiceProperties();
sp.setSendRenew(false);
sp.setService("https://mycompany.com/bigWebApp/login/cas");
CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint();
ep.setLoginUrl("https://cas/login");
ep.setServiceProperties(sp);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/some_path");
MockHttpServletResponse response = new MockHttpServletResponse();
ep.afterPropertiesSet();
ep.commence(request, response, null);
assertThat(
"https://cas/login?service=" + URLEncoder.encode("https://mycompany.com/bigWebApp/login/cas", "UTF-8"))
.isEqualTo(response.getRedirectedUrl());
}
@Test
public void testNormalOperationWithRenewTrue() throws Exception {
ServiceProperties sp = new ServiceProperties();
sp.setSendRenew(true);
sp.setService("https://mycompany.com/bigWebApp/login/cas");
CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint();
ep.setLoginUrl("https://cas/login");
ep.setServiceProperties(sp);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setRequestURI("/some_path");
MockHttpServletResponse response = new MockHttpServletResponse();
ep.afterPropertiesSet();
ep.commence(request, response, null);
assertThat("https://cas/login?service="
+ URLEncoder.encode("https://mycompany.com/bigWebApp/login/cas", "UTF-8") + "&renew=true")
.isEqualTo(response.getRedirectedUrl());
}
}
@@ -0,0 +1,235 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web;
import javax.servlet.FilterChain;
import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.context.SecurityContextRepository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests {@link CasAuthenticationFilter}.
*
* @author Ben Alex
* @author Rob Winch
*/
public class CasAuthenticationFilterTests {
@AfterEach
public void tearDown() {
SecurityContextHolder.clearContext();
}
@Test
public void testGettersSetters() {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setProxyGrantingTicketStorage(mock(ProxyGrantingTicketStorage.class));
filter.setProxyReceptorUrl("/someurl");
filter.setServiceProperties(new ServiceProperties());
}
@Test
public void testNormalOperation() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setServletPath("/login/cas");
request.addParameter("ticket", "ST-0-ER94xMJmn6pha35CQRoZ");
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setAuthenticationManager((a) -> a);
assertThat(filter.requiresAuthentication(request, new MockHttpServletResponse())).isTrue();
Authentication result = filter.attemptAuthentication(request, new MockHttpServletResponse());
assertThat(result != null).isTrue();
}
@Test
public void testNullServiceTicketHandledGracefully() throws Exception {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setAuthenticationManager((a) -> {
throw new BadCredentialsException("Rejected");
});
assertThatExceptionOfType(AuthenticationException.class).isThrownBy(
() -> filter.attemptAuthentication(new MockHttpServletRequest(), new MockHttpServletResponse()));
}
@Test
public void testRequiresAuthenticationFilterProcessUrl() {
String url = "/login/cas";
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setFilterProcessesUrl(url);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
request.setServletPath(url);
assertThat(filter.requiresAuthentication(request, response)).isTrue();
}
@Test
public void testRequiresAuthenticationProxyRequest() {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
request.setServletPath("/pgtCallback");
assertThat(filter.requiresAuthentication(request, response)).isFalse();
filter.setProxyReceptorUrl(request.getServletPath());
assertThat(filter.requiresAuthentication(request, response)).isFalse();
filter.setProxyGrantingTicketStorage(mock(ProxyGrantingTicketStorage.class));
assertThat(filter.requiresAuthentication(request, response)).isTrue();
request.setServletPath("/other");
assertThat(filter.requiresAuthentication(request, response)).isFalse();
}
@Test
public void testRequiresAuthenticationAuthAll() {
ServiceProperties properties = new ServiceProperties();
properties.setAuthenticateAllArtifacts(true);
String url = "/login/cas";
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setFilterProcessesUrl(url);
filter.setServiceProperties(properties);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
request.setServletPath(url);
assertThat(filter.requiresAuthentication(request, response)).isTrue();
request.setServletPath("/other");
assertThat(filter.requiresAuthentication(request, response)).isFalse();
request.setParameter(properties.getArtifactParameter(), "value");
assertThat(filter.requiresAuthentication(request, response)).isTrue();
SecurityContextHolder.getContext()
.setAuthentication(new AnonymousAuthenticationToken("key", "principal",
AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")));
assertThat(filter.requiresAuthentication(request, response)).isTrue();
SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("un", "principal"));
assertThat(filter.requiresAuthentication(request, response)).isTrue();
SecurityContextHolder.getContext()
.setAuthentication(new TestingAuthenticationToken("un", "principal", "ROLE_ANONYMOUS"));
assertThat(filter.requiresAuthentication(request, response)).isFalse();
}
@Test
public void testAuthenticateProxyUrl() throws Exception {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
request.setServletPath("/pgtCallback");
filter.setProxyGrantingTicketStorage(mock(ProxyGrantingTicketStorage.class));
filter.setProxyReceptorUrl(request.getServletPath());
assertThat(filter.attemptAuthentication(request, response)).isNull();
}
@Test
public void testDoFilterAuthenticateAll() throws Exception {
AuthenticationSuccessHandler successHandler = mock(AuthenticationSuccessHandler.class);
AuthenticationManager manager = mock(AuthenticationManager.class);
Authentication authentication = new TestingAuthenticationToken("un", "pwd", "ROLE_USER");
given(manager.authenticate(any(Authentication.class))).willReturn(authentication);
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setAuthenticateAllArtifacts(true);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("ticket", "ST-1-123");
request.setServletPath("/authenticate");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setServiceProperties(serviceProperties);
filter.setAuthenticationSuccessHandler(successHandler);
filter.setProxyGrantingTicketStorage(mock(ProxyGrantingTicketStorage.class));
filter.setAuthenticationManager(manager);
filter.afterPropertiesSet();
filter.doFilter(request, response, chain);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull()
.withFailMessage("Authentication should not be null");
verify(chain).doFilter(request, response);
verifyNoMoreInteractions(successHandler);
// validate for when the filterProcessUrl matches
filter.setFilterProcessesUrl(request.getServletPath());
SecurityContextHolder.clearContext();
filter.doFilter(request, response, chain);
verifyNoMoreInteractions(chain);
verify(successHandler).onAuthenticationSuccess(request, response, authentication);
}
@Test
public void testSecurityContextHolder() throws Exception {
SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class);
AuthenticationManager manager = mock(AuthenticationManager.class);
Authentication authentication = new TestingAuthenticationToken("un", "pwd", "ROLE_USER");
given(manager.authenticate(any(Authentication.class))).willReturn(authentication);
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setAuthenticateAllArtifacts(true);
MockHttpServletRequest request = new MockHttpServletRequest();
request.setParameter("ticket", "ST-1-123");
request.setServletPath("/authenticate");
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setServiceProperties(serviceProperties);
filter.setProxyGrantingTicketStorage(mock(ProxyGrantingTicketStorage.class));
filter.setAuthenticationManager(manager);
filter.setSecurityContextRepository(securityContextRepository);
filter.afterPropertiesSet();
filter.doFilter(request, response, chain);
assertThat(SecurityContextHolder.getContext().getAuthentication()).isNotNull()
.withFailMessage("Authentication should not be null");
verify(chain).doFilter(request, response);
// validate for when the filterProcessUrl matches
filter.setFilterProcessesUrl(request.getServletPath());
SecurityContextHolder.clearContext();
filter.doFilter(request, response, chain);
ArgumentCaptor<SecurityContext> contextArg = ArgumentCaptor.forClass(SecurityContext.class);
verify(securityContextRepository).saveContext(contextArg.capture(), eq(request), eq(response));
assertThat(contextArg.getValue().getAuthentication().getPrincipal()).isEqualTo(authentication.getName());
}
// SEC-1592
@Test
public void testChainNotInvokedForProxyReceptor() throws Exception {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain chain = mock(FilterChain.class);
request.setServletPath("/pgtCallback");
filter.setProxyGrantingTicketStorage(mock(ProxyGrantingTicketStorage.class));
filter.setProxyReceptorUrl(request.getServletPath());
filter.doFilter(request, response, chain);
verifyNoMoreInteractions(chain);
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web;
import org.junit.jupiter.api.Test;
import org.springframework.security.cas.SamlServiceProperties;
import org.springframework.security.cas.ServiceProperties;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
/**
* Tests {@link ServiceProperties}.
*
* @author Ben Alex
*/
public class ServicePropertiesTests {
@Test
public void detectsMissingService() throws Exception {
ServiceProperties sp = new ServiceProperties();
assertThatIllegalArgumentException().isThrownBy(sp::afterPropertiesSet);
}
@Test
public void nullServiceWhenAuthenticateAllTokens() throws Exception {
ServiceProperties sp = new ServiceProperties();
sp.setAuthenticateAllArtifacts(true);
assertThatIllegalArgumentException().isThrownBy(sp::afterPropertiesSet);
sp.setAuthenticateAllArtifacts(false);
assertThatIllegalArgumentException().isThrownBy(sp::afterPropertiesSet);
}
@Test
public void testGettersSetters() throws Exception {
ServiceProperties[] sps = { new ServiceProperties(), new SamlServiceProperties() };
for (ServiceProperties sp : sps) {
sp.setSendRenew(false);
assertThat(sp.isSendRenew()).isFalse();
sp.setSendRenew(true);
assertThat(sp.isSendRenew()).isTrue();
sp.setArtifactParameter("notticket");
assertThat(sp.getArtifactParameter()).isEqualTo("notticket");
sp.setServiceParameter("notservice");
assertThat(sp.getServiceParameter()).isEqualTo("notservice");
sp.setService("https://mycompany.com/service");
assertThat(sp.getService()).isEqualTo("https://mycompany.com/service");
sp.afterPropertiesSet();
}
}
}
@@ -0,0 +1,132 @@
/*
* Copyright 2011-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.cas.web.authentication;
import java.util.regex.Pattern;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.web.util.UrlUtils;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Rob Winch
*/
public class DefaultServiceAuthenticationDetailsTests {
private DefaultServiceAuthenticationDetails details;
private MockHttpServletRequest request;
private Pattern artifactPattern;
private String casServiceUrl;
private ConfigurableApplicationContext context;
@BeforeEach
public void setUp() {
this.casServiceUrl = "https://localhost:8443/j_spring_security_cas";
this.request = new MockHttpServletRequest();
this.request.setScheme("https");
this.request.setServerName("localhost");
this.request.setServerPort(8443);
this.request.setRequestURI("/cas-sample/secure/");
this.artifactPattern = DefaultServiceAuthenticationDetails
.createArtifactPattern(ServiceProperties.DEFAULT_CAS_ARTIFACT_PARAMETER);
}
@AfterEach
public void cleanup() {
if (this.context != null) {
this.context.close();
}
}
@Test
public void getServiceUrlNullQuery() throws Exception {
this.details = new DefaultServiceAuthenticationDetails(this.casServiceUrl, this.request, this.artifactPattern);
assertThat(this.details.getServiceUrl()).isEqualTo(UrlUtils.buildFullRequestUrl(this.request));
}
@Test
public void getServiceUrlTicketOnlyParam() throws Exception {
this.request.setQueryString("ticket=123");
this.details = new DefaultServiceAuthenticationDetails(this.casServiceUrl, this.request, this.artifactPattern);
String serviceUrl = this.details.getServiceUrl();
this.request.setQueryString(null);
assertThat(serviceUrl).isEqualTo(UrlUtils.buildFullRequestUrl(this.request));
}
@Test
public void getServiceUrlTicketFirstMultiParam() throws Exception {
this.request.setQueryString("ticket=123&other=value");
this.details = new DefaultServiceAuthenticationDetails(this.casServiceUrl, this.request, this.artifactPattern);
String serviceUrl = this.details.getServiceUrl();
this.request.setQueryString("other=value");
assertThat(serviceUrl).isEqualTo(UrlUtils.buildFullRequestUrl(this.request));
}
@Test
public void getServiceUrlTicketLastMultiParam() throws Exception {
this.request.setQueryString("other=value&ticket=123");
this.details = new DefaultServiceAuthenticationDetails(this.casServiceUrl, this.request, this.artifactPattern);
String serviceUrl = this.details.getServiceUrl();
this.request.setQueryString("other=value");
assertThat(serviceUrl).isEqualTo(UrlUtils.buildFullRequestUrl(this.request));
}
@Test
public void getServiceUrlTicketMiddleMultiParam() throws Exception {
this.request.setQueryString("other=value&ticket=123&last=this");
this.details = new DefaultServiceAuthenticationDetails(this.casServiceUrl, this.request, this.artifactPattern);
String serviceUrl = this.details.getServiceUrl();
this.request.setQueryString("other=value&last=this");
assertThat(serviceUrl).isEqualTo(UrlUtils.buildFullRequestUrl(this.request));
}
@Test
public void getServiceUrlDoesNotUseHostHeader() throws Exception {
this.casServiceUrl = "https://example.com/j_spring_security_cas";
this.request.setServerName("evil.com");
this.details = new DefaultServiceAuthenticationDetails(this.casServiceUrl, this.request, this.artifactPattern);
assertThat(this.details.getServiceUrl()).isEqualTo("https://example.com/cas-sample/secure/");
}
@Test
public void getServiceUrlDoesNotUseHostHeaderExplicit() {
this.casServiceUrl = "https://example.com/j_spring_security_cas";
this.request.setServerName("evil.com");
ServiceAuthenticationDetails details = loadServiceAuthenticationDetails(
"defaultserviceauthenticationdetails-explicit.xml");
assertThat(details.getServiceUrl()).isEqualTo("https://example.com/cas-sample/secure/");
}
private ServiceAuthenticationDetails loadServiceAuthenticationDetails(String resourceName) {
this.context = new GenericXmlApplicationContext(getClass(), resourceName);
ServiceAuthenticationDetailsSource source = this.context.getBean(ServiceAuthenticationDetailsSource.class);
return source.buildDetails(this.request);
}
}
+15
View File
@@ -0,0 +1,15 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.springframework.security" level="${sec.log.level:-WARN}"/>
<root level="${root.level:-WARN}">
<appender-ref ref="STDOUT" />
</root>
</configuration>
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="serviceProperties"
class="org.springframework.security.cas.ServiceProperties">
<property name="service"
value="https://example.com/j_spring_security_cas"/>
<property name="sendRenew" value="false"/>
</bean>
<bean id="serviceProperties2"
class="org.springframework.security.cas.ServiceProperties">
<property name="service"
value="https://example2.com/j_spring_security_cas"/>
<property name="sendRenew" value="false"/>
</bean>
<bean class="org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource">
<constructor-arg ref="serviceProperties"/>
</bean>
</beans>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="serviceProperties"
class="org.springframework.security.cas.ServiceProperties">
<property name="service"
value="https://example.com/j_spring_security_cas"/>
<property name="sendRenew" value="false"/>
</bean>
<bean class="org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource"/>
</beans>
+54 -25
View File
@@ -21,6 +21,7 @@ dependencies {
optional project(':spring-security-oauth2-client')
optional project(':spring-security-oauth2-jose')
optional project(':spring-security-oauth2-resource-server')
optional project(':spring-security-openid')
optional project(':spring-security-rsocket')
optional project(':spring-security-web')
optional 'io.projectreactor:reactor-core'
@@ -39,15 +40,15 @@ dependencies {
provided 'jakarta.servlet:jakarta.servlet-api'
testImplementation project(':spring-security-aspects')
testImplementation project(':spring-security-cas')
testImplementation project(':spring-security-test')
testImplementation project(path : ':spring-security-core', configuration : 'tests')
testImplementation project(path : ':spring-security-ldap', configuration : 'tests')
testImplementation project(path : ':spring-security-oauth2-client', configuration : 'tests')
testImplementation project(path : ':spring-security-oauth2-resource-server', configuration : 'tests')
testImplementation project(':spring-security-saml2-service-provider')
testImplementation project(path : ':spring-security-saml2-service-provider', configuration : 'tests')
testImplementation project(path : ':spring-security-saml2-service-provider', configuration : 'opensaml4MainImplementation')
testImplementation project(path : ':spring-security-web', configuration : 'tests')
testImplementation "jakarta.inject:jakarta.inject-api"
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-params"
@@ -59,14 +60,13 @@ dependencies {
testImplementation 'ch.qos.logback:logback-classic'
testImplementation 'io.projectreactor.netty:reactor-netty'
testImplementation 'io.rsocket:rsocket-transport-netty'
testImplementation 'jakarta.annotation:jakarta.annotation-api'
testImplementation 'jakarta.annotation:jakarta.annotation-api:1.0'
testImplementation "jakarta.inject:jakarta.inject-api"
testImplementation "jakarta.transaction:jakarta.transaction-api"
testImplementation 'jakarta.xml.bind:jakarta.xml.bind-api'
testImplementation 'jakarta.websocket:jakarta.websocket-api'
testImplementation 'jakarta.websocket:jakarta.websocket-client-api'
testImplementation 'ldapsdk:ldapsdk:4.1'
testImplementation('net.sourceforge.htmlunit:htmlunit') {
exclude group: 'commons-logging', module: 'commons-logging'
exclude group: 'xml-apis', module: 'xml-apis'
}
testImplementation "org.apache.directory.server:apacheds-core"
testImplementation "org.apache.directory.server:apacheds-core-entry"
@@ -75,14 +75,22 @@ dependencies {
testImplementation "org.apache.directory.server:apacheds-server-jndi"
testImplementation 'org.apache.directory.shared:shared-ldap'
testImplementation "com.unboundid:unboundid-ldapsdk"
testImplementation 'jakarta.persistence:jakarta.persistence-api'
testImplementation "org.hibernate.orm:hibernate-core"
testImplementation 'org.eclipse.persistence:javax.persistence'
testImplementation('org.hibernate:hibernate-entitymanager') {
exclude group: 'javax.activation', module: 'javax.activation-api'
exclude group: 'javax.persistence', module: 'javax.persistence-api'
exclude group: 'javax.xml.bind', module: 'jaxb-api'
exclude group: 'org.jboss.spec.javax.transaction', module: 'jboss-transaction-api_1.2_spec'
}
testImplementation 'org.hsqldb:hsqldb'
testImplementation 'org.mockito:mockito-core'
testImplementation "org.mockito:mockito-inline"
testImplementation ('org.openid4java:openid4java-nodeps') {
exclude group: 'com.google.code.guice', module: 'guice'
exclude group: 'commons-logging', module: 'commons-logging'
}
testImplementation('org.seleniumhq.selenium:htmlunit-driver') {
exclude group: 'commons-logging', module: 'commons-logging'
exclude group: 'xml-apis', module: 'xml-apis'
}
testImplementation('org.seleniumhq.selenium:selenium-java') {
exclude group: 'commons-logging', module: 'commons-logging'
@@ -93,25 +101,12 @@ dependencies {
testImplementation 'org.springframework:spring-jdbc'
testImplementation 'org.springframework:spring-orm'
testImplementation 'org.springframework:spring-tx'
testImplementation 'org.springframework:spring-core-test'
testImplementation ('org.springframework.data:spring-data-jpa') {
exclude group: 'org.aspectj', module: 'aspectjrt'
}
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-reactor'
testImplementation 'io.mockk:mockk'
testImplementation 'org.junit.platform:junit-platform-launcher'
testImplementation 'org.apache.maven.resolver:maven-resolver-connector-basic'
testImplementation ('org.apache.maven.resolver:maven-resolver-impl') {
exclude(group: "javax.annotation", module: "javax.annotation-api")
}
testImplementation ('org.apache.maven:maven-resolver-provider') {
exclude(group: "javax.inject", module: "javax.inject")
exclude(group: "javax.annotation", module: "javax.annotation-api")
}
testImplementation ('org.apache.maven.resolver:maven-resolver-transport-http') {
exclude group: "org.slf4j", module: "jcl-over-slf4j"
}
testRuntimeOnly 'org.hsqldb:hsqldb'
}
@@ -147,9 +142,43 @@ tasks.named('sourcesJar', Jar).configure {
tasks.withType(KotlinCompile).configureEach {
kotlinOptions {
languageVersion = "1.7"
apiVersion = "1.7"
languageVersion = "1.3"
apiVersion = "1.3"
freeCompilerArgs = ["-Xjsr305=strict", "-Xsuppress-version-warnings"]
jvmTarget = "17"
jvmTarget = "1.8"
}
}
compileTestJava {
exclude "org/springframework/security/config/annotation/web/configurers/saml2/**", "org/springframework/security/config/http/Saml2*"
}
task compileSaml2TestJava(type: JavaCompile) {
javaCompiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(11)
}
source = sourceSets.test.java.srcDirs
include "org/springframework/security/config/annotation/web/configurers/saml2/**", "org/springframework/security/config/http/Saml2*"
classpath = sourceSets.test.compileClasspath
destinationDirectory = new File("${buildDir}/classes/java/test")
options.sourcepath = sourceSets.test.java.getSourceDirectories()
}
task saml2Tests(type: Test) {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(11)
}
filter {
includeTestsMatching "org.springframework.security.config.annotation.web.configurers.saml2.*"
}
useJUnitPlatform()
dependsOn compileSaml2TestJava
}
test {
shouldRunAfter saml2Tests
}
tasks.named('check') {
dependsOn saml2Tests
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -28,15 +28,14 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.configuration.ObjectPostProcessorConfiguration;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
@@ -70,6 +69,7 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests {
public void defaultConfiguration() {
this.spring.register(DefaultLdapConfig.class).autowire();
LdapAuthenticationProvider provider = ldapProvider();
LdapAuthoritiesPopulator authoritiesPopulator = getAuthoritiesPopulator(provider);
assertThat(authoritiesPopulator).hasFieldOrPropertyWithValue("groupRoleAttribute", "cn");
assertThat(authoritiesPopulator).hasFieldOrPropertyWithValue("groupSearchBase", "");
@@ -157,12 +157,11 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests {
return port;
}
@Configuration
@EnableWebSecurity
static class DefaultLdapConfig extends BaseLdapProviderConfig {
@Autowired
void configure(AuthenticationManagerBuilder auth) throws Exception {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.ldapAuthentication()
@@ -171,20 +170,13 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests {
// @formatter:on
}
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
@Configuration
@EnableWebSecurity
static class GroupRolesConfig extends BaseLdapProviderConfig {
@Autowired
void configure(AuthenticationManagerBuilder auth) throws Exception {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.ldapAuthentication()
@@ -194,20 +186,13 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests {
// @formatter:on
}
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
@Configuration
@EnableWebSecurity
static class GroupSearchConfig extends BaseLdapProviderConfig {
@Autowired
void configure(AuthenticationManagerBuilder auth) throws Exception {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.ldapAuthentication()
@@ -217,20 +202,13 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests {
// @formatter:on
}
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
@Configuration
@EnableWebSecurity
static class GroupSubtreeSearchConfig extends BaseLdapProviderConfig {
@Autowired
void configure(AuthenticationManagerBuilder auth) throws Exception {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.ldapAuthentication()
@@ -241,20 +219,13 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests {
// @formatter:on
}
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
@Configuration
@EnableWebSecurity
static class RolePrefixConfig extends BaseLdapProviderConfig {
@Autowired
void configure(AuthenticationManagerBuilder auth) throws Exception {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.ldapAuthentication()
@@ -264,20 +235,13 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests {
// @formatter:on
}
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
@Configuration
@EnableWebSecurity
static class BindAuthenticationConfig extends BaseLdapServerConfig {
@Autowired
void configure(AuthenticationManagerBuilder auth) throws Exception {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.ldapAuthentication()
@@ -288,20 +252,13 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests {
// @formatter:on
}
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
@Configuration
@EnableWebSecurity
static class PasswordEncoderConfig extends BaseLdapServerConfig {
@Autowired
void configure(AuthenticationManagerBuilder auth) throws Exception {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.ldapAuthentication()
@@ -313,15 +270,8 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests {
// @formatter:on
}
@Bean
AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
@Configuration
@EnableWebSecurity
abstract static class BaseLdapServerConfig extends BaseLdapProviderConfig {
@@ -335,11 +285,10 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests {
}
@Configuration
@EnableWebSecurity
@EnableGlobalAuthentication
@Import(ObjectPostProcessorConfiguration.class)
abstract static class BaseLdapProviderConfig {
abstract static class BaseLdapProviderConfig extends WebSecurityConfigurerAdapter {
@Bean
BaseLdapPathContextSource contextSource() throws Exception {
@@ -351,6 +300,15 @@ public class LdapAuthenticationProviderBuilderSecurityBuilderTests {
return contextSource;
}
@Bean
AuthenticationManager authenticationManager(AuthenticationManagerBuilder auth) throws Exception {
configure(auth);
return auth.build();
}
@Override
protected abstract void configure(AuthenticationManagerBuilder auth) throws Exception;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,10 +22,10 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.ldap.LdapAuthenticationProviderBuilderSecurityBuilderTests.BaseLdapProviderConfig;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.authority.AuthorityUtils;
@@ -104,22 +104,21 @@ public class LdapAuthenticationProviderConfigurerTests {
// @formatter:off
SecurityMockMvcRequestBuilders.FormLoginRequestBuilder request = formLogin()
.user("otherben")
.password("otherbenspassword");
.user("ben")
.password("benspassword");
SecurityMockMvcResultMatchers.AuthenticatedMatcher expectedUser = authenticated()
.withUsername("otherben")
.withUsername("ben")
.withAuthorities(
AuthorityUtils.createAuthorityList("ROLE_SUBMANAGERS", "ROLE_MANAGERS", "ROLE_DEVELOPERS"));
// @formatter:on
this.mockMvc.perform(request).andExpect(expectedUser);
}
@Configuration
@EnableWebSecurity
static class MultiLdapAuthenticationProvidersConfig {
static class MultiLdapAuthenticationProvidersConfig extends WebSecurityConfigurerAdapter {
@Autowired
void configure(AuthenticationManagerBuilder auth) throws Exception {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.ldapAuthentication()
@@ -136,12 +135,11 @@ public class LdapAuthenticationProviderConfigurerTests {
}
@Configuration
@EnableWebSecurity
static class MultiLdapWithCustomRolePrefixAuthenticationProvidersConfig {
static class MultiLdapWithCustomRolePrefixAuthenticationProvidersConfig extends WebSecurityConfigurerAdapter {
@Autowired
void configure(AuthenticationManagerBuilder auth) throws Exception {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.ldapAuthentication()
@@ -160,12 +158,11 @@ public class LdapAuthenticationProviderConfigurerTests {
}
@Configuration
@EnableWebSecurity
static class LdapWithRandomPortConfig {
static class LdapWithRandomPortConfig extends WebSecurityConfigurerAdapter {
@Autowired
void configure(AuthenticationManagerBuilder auth) throws Exception {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.ldapAuthentication()
@@ -173,18 +170,17 @@ public class LdapAuthenticationProviderConfigurerTests {
.groupSearchFilter("(member={0})")
.userDnPatterns("uid={0},ou=people")
.contextSource()
.port(0);
.port(0);
// @formatter:on
}
}
@Configuration
@EnableWebSecurity
static class GroupSubtreeSearchConfig extends BaseLdapProviderConfig {
@Autowired
void configure(AuthenticationManagerBuilder auth) throws Exception {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.ldapAuthentication()
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +16,9 @@
package org.springframework.security.config.annotation.authentication.ldap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator;
import org.springframework.security.ldap.userdetails.PersonContextMapper;
@@ -30,12 +29,11 @@ import org.springframework.security.ldap.userdetails.PersonContextMapper;
*/
public class NamespaceLdapAuthenticationProviderTestsConfigs {
@Configuration
@EnableWebSecurity
static class LdapAuthenticationProviderConfig {
static class LdapAuthenticationProviderConfig extends WebSecurityConfigurerAdapter {
@Autowired
void configure(AuthenticationManagerBuilder auth) throws Exception {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.ldapAuthentication()
@@ -46,12 +44,11 @@ public class NamespaceLdapAuthenticationProviderTestsConfigs {
}
@Configuration
@EnableWebSecurity
static class CustomLdapAuthenticationProviderConfig {
static class CustomLdapAuthenticationProviderConfig extends WebSecurityConfigurerAdapter {
@Autowired
void configure(AuthenticationManagerBuilder auth) throws Exception {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.ldapAuthentication()
@@ -70,20 +67,19 @@ public class NamespaceLdapAuthenticationProviderTestsConfigs {
.managerPassword("secret") // ldap-server@manager-password
.port(0) // ldap-server@port
.root("dc=springframework,dc=org"); // ldap-server@root
// .url("ldap://localhost:33389/dc-springframework,dc=org") this overrides root and port and is used for external
// .url("ldap://localhost:33389/dc-springframework,dc=org") this overrides root and port and is used for external
// @formatter:on
}
}
@Configuration
@EnableWebSecurity
static class CustomAuthoritiesPopulatorConfig {
static class CustomAuthoritiesPopulatorConfig extends WebSecurityConfigurerAdapter {
static LdapAuthoritiesPopulator LAP;
@Autowired
void configure(AuthenticationManagerBuilder auth) throws Exception {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.ldapAuthentication()
@@ -94,12 +90,11 @@ public class NamespaceLdapAuthenticationProviderTestsConfigs {
}
@Configuration
@EnableWebSecurity
static class PasswordCompareLdapConfig {
static class PasswordCompareLdapConfig extends WebSecurityConfigurerAdapter {
@Autowired
void configure(AuthenticationManagerBuilder auth) throws Exception {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.ldapAuthentication()
@@ -22,7 +22,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.UnsatisfiedDependencyException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -78,12 +77,10 @@ public class EmbeddedLdapServerContextSourceFactoryBeanITests {
public void contextSourceFactoryBeanWhenManagerDnAndNoPasswordThenException() {
assertThatExceptionOfType(UnsatisfiedDependencyException.class)
.isThrownBy(() -> this.spring.register(CustomManagerDnNoPasswordConfig.class).autowire())
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withRootCauseInstanceOf(IllegalStateException.class)
.withMessageContaining("managerPassword is required if managerDn is supplied");
}
@Configuration
@EnableWebSecurity
static class FromEmbeddedLdapServerConfig {
@@ -101,7 +98,6 @@ public class EmbeddedLdapServerContextSourceFactoryBeanITests {
}
@Configuration
@EnableWebSecurity
static class PortZeroConfig {
@@ -122,7 +118,6 @@ public class EmbeddedLdapServerContextSourceFactoryBeanITests {
}
@Configuration
@EnableWebSecurity
static class CustomLdifAndRootConfig {
@@ -144,7 +139,6 @@ public class EmbeddedLdapServerContextSourceFactoryBeanITests {
}
@Configuration
@EnableWebSecurity
static class CustomManagerDnConfig {
@@ -167,7 +161,6 @@ public class EmbeddedLdapServerContextSourceFactoryBeanITests {
}
@Configuration
@EnableWebSecurity
static class CustomManagerDnNoPasswordConfig {
@@ -27,7 +27,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
@@ -134,7 +133,6 @@ public class LdapBindAuthenticationManagerFactoryITests {
.andExpect(authenticated().withUsername("bob"));
}
@Configuration
@EnableWebSecurity
static class FromContextSourceConfig extends BaseLdapServerConfig {
@@ -147,7 +145,6 @@ public class LdapBindAuthenticationManagerFactoryITests {
}
@Configuration
@EnableWebSecurity
static class CustomAuthoritiesMapperConfig extends BaseLdapServerConfig {
@@ -163,7 +160,6 @@ public class LdapBindAuthenticationManagerFactoryITests {
}
@Configuration
@EnableWebSecurity
static class CustomAuthoritiesPopulatorConfig extends BaseLdapServerConfig {
@@ -179,7 +175,6 @@ public class LdapBindAuthenticationManagerFactoryITests {
}
@Configuration
@EnableWebSecurity
static class CustomUserDetailsContextMapperConfig extends BaseLdapServerConfig {
@@ -195,7 +190,6 @@ public class LdapBindAuthenticationManagerFactoryITests {
}
@Configuration
@EnableWebSecurity
static class CustomUserDnPatternsConfig extends BaseLdapServerConfig {
@@ -208,7 +202,6 @@ public class LdapBindAuthenticationManagerFactoryITests {
}
@Configuration
@EnableWebSecurity
static class CustomUserSearchConfig extends BaseLdapServerConfig {
@@ -222,7 +215,6 @@ public class LdapBindAuthenticationManagerFactoryITests {
}
@Configuration
@EnableWebSecurity
abstract static class BaseLdapServerConfig implements DisposableBean {
@@ -22,7 +22,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -60,7 +59,6 @@ public class LdapPasswordComparisonAuthenticationManagerFactoryITests {
this.mockMvc.perform(formLogin().user("bob").password("bob")).andExpect(authenticated().withUsername("bob"));
}
@Configuration
@EnableWebSecurity
static class CustomPasswordEncoderConfig extends BaseLdapServerConfig {
@@ -74,7 +72,6 @@ public class LdapPasswordComparisonAuthenticationManagerFactoryITests {
}
@Configuration
@EnableWebSecurity
static class CustomPasswordAttributeConfig extends BaseLdapServerConfig {
@@ -89,7 +86,6 @@ public class LdapPasswordComparisonAuthenticationManagerFactoryITests {
}
@Configuration
@EnableWebSecurity
abstract static class BaseLdapServerConfig implements DisposableBean {
@@ -28,16 +28,6 @@ sn: Alex
uid: ben
userPassword: {SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=
dn: uid=otherben,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Other Ben Alex
sn: Alex
uid: otherben
userPassword: otherbenspassword
dn: uid=bcrypt,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
@@ -85,7 +75,6 @@ cn: developers
ou: developer
member: uid=bcrypt,ou=people,dc=springframework,dc=org
member: uid=ben,ou=people,dc=springframework,dc=org
member: uid=otherben,ou=people,dc=springframework,dc=org
member: uid=bob,ou=people,dc=springframework,dc=org
dn: cn=managers,ou=groups,dc=springframework,dc=org
@@ -94,7 +83,6 @@ objectclass: groupOfNames
cn: managers
ou: manager
member: uid=ben,ou=people,dc=springframework,dc=org
member: uid=otherben,ou=people,dc=springframework,dc=org
member: cn=mouse\, jerry,ou=people,dc=springframework,dc=org
dn: cn=submanagers,ou=subgroups,ou=groups,dc=springframework,dc=org
@@ -103,4 +91,3 @@ objectclass: groupOfNames
cn: submanagers
ou: submanager
member: uid=ben,ou=people,dc=springframework,dc=org
member: uid=otherben,ou=people,dc=springframework,dc=org
@@ -72,6 +72,12 @@ public abstract class Elements {
public static final String FORM_LOGIN = "form-login";
public static final String OPENID_LOGIN = "openid-login";
public static final String OPENID_ATTRIBUTE_EXCHANGE = "attribute-exchange";
public static final String OPENID_ATTRIBUTE = "openid-attribute";
public static final String BASIC_AUTH = "http-basic";
public static final String REMEMBER_ME = "remember-me";
@@ -96,7 +96,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
pc.getReaderContext()
.fatal("You cannot use a spring-security-2.0.xsd or spring-security-3.0.xsd or "
+ "spring-security-3.1.xsd schema or spring-security-3.2.xsd schema or spring-security-4.0.xsd schema "
+ "with Spring Security 6.0. Please update your schema declarations to the 6.0 schema.",
+ "with Spring Security 5.8. Please update your schema declarations to the 5.8 schema.",
element);
}
String name = pc.getDelegate().getLocalName(element);
@@ -221,7 +221,7 @@ public final class SecurityNamespaceHandler implements NamespaceHandler {
private boolean matchesVersionInternal(Element element) {
String schemaLocation = element.getAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "schemaLocation");
return schemaLocation.matches("(?m).*spring-security-6\\.0.*.xsd.*")
return schemaLocation.matches("(?m).*spring-security-5\\.8.*.xsd.*")
|| schemaLocation.matches("(?m).*spring-security.xsd.*")
|| !schemaLocation.matches("(?m).*spring-security.*");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -149,7 +149,7 @@ public class AuthenticationManagerBuilder
* {@link #getDefaultUserDetailsService()} method. Note that additional
* {@link UserDetailsService}'s may override this {@link UserDetailsService} as the
* default. See the <a href=
* "https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#user-schema"
* "https://docs.spring.io/spring-security/reference/servlet/appendix/database-schema.html"
* >User Schema</a> section of the reference for the default schema.
* </p>
* @return a {@link JdbcUserDetailsManagerConfigurer} to allow customization of the
@@ -1,62 +0,0 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.config.annotation.authentication.configuration;
import java.util.Set;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.ProxyHints;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationCode;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.util.ClassUtils;
/**
* AOT {@code BeanRegistrationAotProcessor} that detects beans that implement
* {@link AuthenticationManager} creates the required proxy hints.
*
* @author Marcus da Coregio
* @since 6.0.1
* @see AuthenticationConfiguration#getAuthenticationManager()
*/
class AuthenticationManagerBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
@Override
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
Class<?> beanClass = registeredBean.getBeanClass();
Set<Class<?>> allInterfacesForClass = ClassUtils.getAllInterfacesForClassAsSet(beanClass);
if (allInterfacesForClass.contains(AuthenticationManager.class)) {
return new AuthenticationManagerBeanRegistrationAotContribution();
}
return null;
}
private static class AuthenticationManagerBeanRegistrationAotContribution
implements BeanRegistrationAotContribution {
@Override
public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) {
ProxyHints proxyHints = generationContext.getRuntimeHints().proxies();
proxyHints.registerJdkProxy(AopProxyUtils.completeJdkProxyInterfaces(AuthenticationManager.class));
}
}
}
@@ -22,6 +22,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
@@ -104,6 +105,7 @@ import org.springframework.security.config.annotation.web.servlet.configuration.
@Target(ElementType.TYPE)
@Documented
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}

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